pyoframe 1.1.0__tar.gz → 1.2.1__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.
- {pyoframe-1.1.0 → pyoframe-1.2.1}/PKG-INFO +3 -3
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/.nav.yml +1 -1
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/contribute/index.md +1 -1
- {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.1/docs/learn/concepts}/.nav.yml +1 -1
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/concepts/addition.md +32 -13
- pyoframe-1.2.1/docs/learn/develop/.nav.yml +8 -0
- pyoframe-1.2.1/docs/learn/develop/create-a-model.md +41 -0
- pyoframe-1.2.1/docs/learn/develop/create-constraints.md +27 -0
- pyoframe-1.2.1/docs/learn/develop/create-expressions.md +95 -0
- pyoframe-1.2.1/docs/learn/develop/create-variables.md +215 -0
- pyoframe-1.2.1/docs/learn/develop/define-objective.md +42 -0
- pyoframe-1.2.1/docs/learn/develop/read-results.md +11 -0
- pyoframe-1.1.0/docs/learn/concepts/solver-access.md → pyoframe-1.2.1/docs/learn/develop/run-and-configure-the-solver.md +33 -19
- pyoframe-1.2.1/docs/learn/get-started/.nav.yml +4 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/example-with-dimensions.md +1 -1
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/installation.md +2 -2
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/migrate/v1.0.md +2 -2
- {pyoframe-1.1.0 → pyoframe-1.2.1}/pyoframe.egg-info/PKG-INFO +3 -3
- {pyoframe-1.1.0 → pyoframe-1.2.1}/pyoframe.egg-info/SOURCES.txt +12 -9
- {pyoframe-1.1.0 → pyoframe-1.2.1}/pyoframe.egg-info/requires.txt +2 -2
- {pyoframe-1.1.0 → pyoframe-1.2.1}/pyproject.toml +2 -2
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_arithmetic.py +3 -2
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_core.py +21 -1
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_model.py +178 -16
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_version.py +3 -3
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_addition.py +53 -2
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_model.py +10 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_names.py +16 -0
- pyoframe-1.2.1/tests/test_objective.py +90 -0
- pyoframe-1.1.0/docs/learn/advanced-concepts/datastructure.md +0 -7
- pyoframe-1.1.0/docs/learn/concepts/.nav.yml +0 -4
- pyoframe-1.1.0/docs/learn/concepts/special-functions.md +0 -12
- pyoframe-1.1.0/docs/learn/get-started/.nav.yml +0 -5
- pyoframe-1.1.0/docs/learn/get-started/basics.md +0 -347
- pyoframe-1.1.0/tests/test_objective.py +0 -22
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.gitattributes +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/CODEOWNERS +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/actions/setup_optimizers_linux/action.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/actions/setup_optimizers_macos/action.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/actions/setup_optimizers_windows/action.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/dependabot.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/ci.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/format.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/lines_changed_counter.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/lint.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/publish_doc.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/publish_doc_dev.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/publish_to_pypi.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.github/workflows/test_doc.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.gitignore +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.pre-commit-config.yaml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.vscode/launch.json +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/.vscode/settings.json +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/CHANGELOG.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/LICENSE +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/README.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/conftest.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/examples/.nav.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/examples/diet.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/examples/facility_location.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/examples/index.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/examples/portfolio_optimization.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/examples/production.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/index.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/javascripts/feedback.js +0 -0
- {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.1/docs/learn/concepts}/internals.md +0 -0
- {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.1/docs/learn/concepts}/performance.md +0 -0
- {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.1/docs/learn/concepts}/quadratics.md +0 -0
- {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.1/docs/learn/concepts}/troubleshooting.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/example.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/food_data.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/foods.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/foods_to_nutrients.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/nutrients.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/results.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/power_grid_example.ipynb +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/three-bus.png +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/overrides/main.html +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/overrides/partials/actions.html +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/overrides/partials/comments.html +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/overrides/partials/integrations/analytics/custom.html +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/.nav.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/bases/.nav.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/bases/BaseBlock.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/bases/BaseOperableBlock.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/index.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/public/.nav.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/public/Config.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/types/.nav.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/reference/types/Operable.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/stylesheets/extra.css +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/why-pyoframe/data_py.parquet +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/why-pyoframe/gen_py.parquet +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/why-pyoframe/index.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/why-pyoframe/pyoframe-performance.ipynb +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/docs/why-pyoframe/three-bus-four-gen.png +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/giscus.json +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/mkdocs.yml +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/pyoframe.egg-info/dependency_links.txt +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/pyoframe.egg-info/top_level.txt +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/scripts/archive/benchmark_assign_ids_constraints.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/scripts/archive/benchmark_assign_ids_constraints_2.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/scripts/archive/benchmark_assign_ids_variables.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/scripts/archive/benchmark_attr_performance.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/scripts/generate_api_reference.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/scripts/griffe_extensions.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/setup.cfg +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_constants.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_model_element.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_monkey_patch.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_objective.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_param.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/src/pyoframe/_utils.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/conftest.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/README.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/input_data/orders.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/input_data/parameters.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/results/objective.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/results/problem-copt-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/results/problem-copt-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/results/problem-highs-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/cutting_stock_problem/results/problem-highs-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/README.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/input_data/foods.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/input_data/foods_to_nutrients.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/input_data/nutrients.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/model_gurobipy.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/Buy.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/Buy_ub.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/max_nutrients.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/min_nutrients.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/objective.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/problem-copt-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/problem-copt-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/problem-highs-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/problem-highs-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/solution-copt-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/solution-copt-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/solution-gurobi-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/solution-gurobi-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/solution-highs-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/diet_problem/results/solution-highs-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_location/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_location/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_location/results/objective.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_location/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_location/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/input_data/plants.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/input_data/transport_costs.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/input_data/wharehouses.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/model_gurobipy.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/objective.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/open.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/problem-copt-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/problem-copt-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/problem-highs-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/problem-highs-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/solution-copt-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/solution-copt-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/solution-gurobi-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/solution-gurobi-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/solution-highs-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/solution-highs-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/facility_problem/results/transport.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/input_data/assets.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/input_data/covariance.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/input_data/portfolio_params.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/con_min_return.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/con_weights_sum.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/objective.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/problem-copt-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/problem-copt-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/problem-highs-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/problem-highs-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/solution-copt-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/solution-copt-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/solution-gurobi-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/solution-gurobi-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/solution-highs-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/solution-highs-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/portfolio_optim/results/weight.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/input_data/machines_availability.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/input_data/processing_times.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/input_data/products_profit.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/objective.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/problem-copt-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/problem-copt-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/problem-highs-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/problem-highs-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/solution-copt-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/solution-copt-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/solution-gurobi-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/solution-gurobi-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/solution-highs-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/solution-highs-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/production_planning/results/solution.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/README.md +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/input_data/elspot-prices_2021_hourly_eur.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/Pump.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/Storage_level.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/Turb.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/objective.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/problem-copt-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/problem-copt-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/problem-highs-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/problem-highs-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/solution-copt-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/solution-copt-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/solution-gurobi-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/solution-gurobi-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/solution-highs-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/pumped_storage/results/solution-highs-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/__init__.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/input_data/initial_numbers.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/model.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/problem-copt-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/problem-copt-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/problem-gurobi-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/problem-gurobi-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/problem-highs-machine.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/problem-highs-pretty.lp +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/solution-copt-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/solution-copt-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/solution-gurobi-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/solution-gurobi-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/solution-highs-machine.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/solution-highs-pretty.sol +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/examples/sudoku/results/solution.csv +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_arithmetic.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_constraint.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_examples.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_io.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_param.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_solver.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/test_variable.py +0 -0
- {pyoframe-1.1.0 → pyoframe-1.2.1}/tests/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyoframe
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Blazing fast linear program interface
|
|
5
5
|
Author-email: Bravos Power <dev@bravospower.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,7 +20,7 @@ Requires-Dist: pyarrow
|
|
|
20
20
|
Requires-Dist: pandas<3
|
|
21
21
|
Requires-Dist: pyoptinterface==0.5.1
|
|
22
22
|
Provides-Extra: highs
|
|
23
|
-
Requires-Dist: highsbox<=1.
|
|
23
|
+
Requires-Dist: highsbox<=1.13.0; extra == "highs"
|
|
24
24
|
Provides-Extra: ipopt
|
|
25
25
|
Requires-Dist: pyoptinterface[nlp]; extra == "ipopt"
|
|
26
26
|
Requires-Dist: llvmlite<=0.46.0; extra == "ipopt"
|
|
@@ -34,7 +34,7 @@ Requires-Dist: pre-commit==4.3.0; extra == "dev"
|
|
|
34
34
|
Requires-Dist: gurobipy==12.0.3; extra == "dev"
|
|
35
35
|
Requires-Dist: coverage==7.10.6; extra == "dev"
|
|
36
36
|
Requires-Dist: ipykernel==6.30.1; extra == "dev"
|
|
37
|
-
Requires-Dist: highsbox<=1.
|
|
37
|
+
Requires-Dist: highsbox<=1.13.0; extra == "dev"
|
|
38
38
|
Requires-Dist: pyoptinterface[nlp]; extra == "dev"
|
|
39
39
|
Requires-Dist: numpy; extra == "dev"
|
|
40
40
|
Provides-Extra: docs
|
|
@@ -54,7 +54,7 @@ We use Ruff for linting and formatting. The pre-commit hooks will run `ruff form
|
|
|
54
54
|
|
|
55
55
|
## Additional tips
|
|
56
56
|
|
|
57
|
-
I recommend skimming or reading the [Internal Details](../learn/
|
|
57
|
+
I recommend skimming or reading the [Internal Details](../learn/concepts/internals.md) page for some background on how Pyoframe works.
|
|
58
58
|
|
|
59
59
|
For core developers:
|
|
60
60
|
|
|
@@ -4,19 +4,13 @@ In Pyoframe, [`Expression`][pyoframe.Expression] objects can be added using the
|
|
|
4
4
|
|
|
5
5
|
However, sometimes an addition is ambiguous or indicative of a potential mistake in your model. In these situations, Pyoframe forces you to use _addition modifiers_ to specify exactly how you'd like the addition to be performed. This safety feature helps prevent and quickly fix mistakes in your model.
|
|
6
6
|
|
|
7
|
-
There are three common addition modifiers in Pyoframe: [`.over(…)`][pyoframe.Expression.over], [`.keep_extras()`][pyoframe.Expression.keep_extras], and [`.drop_extras()`][pyoframe.Expression.drop_extras].
|
|
7
|
+
There are three common addition modifiers in Pyoframe: [`.over(…)`][pyoframe.Expression.over], [`.keep_extras()`][pyoframe.Expression.keep_extras], and [`.drop_extras()`][pyoframe.Expression.drop_extras]. We'll discuss each of these as well as how the bitwise OR operator (`|`) can be used as a shortcut.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
!!! warning "Addition modifiers also apply to subtraction and constraint creation"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Please note that **the addition rules described here also apply to subtraction as well as the `<=` and `>=` operators used to create constraints**. This is because subtraction is actually computed as an addition (`a - b` is computed as `a + (-b)`). Similarly, creating a constraint with the `<=` or `>=` operators involves combining the left and right hand sides using addition (`a <= b` becomes `a + (-b) <= 0`). So, although I may only mention addition from now on, please remember that this page also applies to subtraction and to constraint creation.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
2. [The `.keep_extras()` and `.drop_extras()` addition modifiers](#handling-extra-labels-with-keep_extras-and-drop_extras)
|
|
16
|
-
|
|
17
|
-
3. [Important note on the order of operations of addition modifiers](#order-of-operations-for-addition-modifiers)
|
|
18
|
-
|
|
19
|
-
## Adding expressions with differing dimensions using `.over(…)`
|
|
13
|
+
## `.over(…)`
|
|
20
14
|
|
|
21
15
|
To help catch mistakes, adding expressions with differing dimensions is disallowed by default. [`.over(…)`][pyoframe.Expression.over] overrides this default and **indicates that an addition should be performed by "broadcasting" the differing dimensions.**
|
|
22
16
|
|
|
@@ -62,7 +56,7 @@ Do you understand what happened? The error informs us that `model.air_emissions`
|
|
|
62
56
|
|
|
63
57
|
Benign mistakes like these are relatively common and Pyoframe's error messages help you detect them early. Now, let's examine a case where `.over(…)` is needed.
|
|
64
58
|
|
|
65
|
-
### Example 2: Broadcasting
|
|
59
|
+
### Example 2: Broadcasting
|
|
66
60
|
|
|
67
61
|
Say, you'd like to see what happens if, instead of minimizing total emissions, you were to minimize the emissions of the _most emitting flight_. Mathematically, this is equivalent to minimizing variable `E_max` where `E_max` is constrained to be greater or equal to the emissions of every flight.
|
|
68
62
|
|
|
@@ -114,7 +108,7 @@ Notice how applying `.over("flight_no")` added a dimension `flight_no` with valu
|
|
|
114
108
|
|
|
115
109
|
```
|
|
116
110
|
|
|
117
|
-
##
|
|
111
|
+
## `.keep_extras()` / `.drop_extras()`
|
|
118
112
|
|
|
119
113
|
Addition is performed by pairing the labels in the left `Expression` with those in the right `Expression`. But, what happens when the left and right labels differ?
|
|
120
114
|
|
|
@@ -215,7 +209,32 @@ Option 2 hardly seems reasonable this time considering that air emissions make u
|
|
|
215
209
|
|
|
216
210
|
```
|
|
217
211
|
|
|
218
|
-
##
|
|
212
|
+
## The bitwise OR operator
|
|
213
|
+
|
|
214
|
+
In practice, you'll find that it is common to want to keep extra labels on both sides of an addition or subtraction. As such, Pyoframe offers the bitwise OR operator (`|`) as a convenient shortcut. Instead of
|
|
215
|
+
|
|
216
|
+
<!-- invisible-code-block: python
|
|
217
|
+
a = model.air_emissions
|
|
218
|
+
b = model.ground_emissions
|
|
219
|
+
-->
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
a.keep_extras() + b.keep_extras()
|
|
223
|
+
```
|
|
224
|
+
you can simply write
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
a | b
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
For subtraction, the following lines are equivalent:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
a.keep_extras() - b.keep_extras()
|
|
234
|
+
a | -b
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Note on order of operations
|
|
219
238
|
|
|
220
239
|
When an operation creates a new [Expression][pyoframe.Expression], any previously applied addition modifiers are discarded to prevent unexpected behaviors. As such, **addition modifiers only work if they're applied _right before_ an addition**. For example, `a.drop_extras().sum("time") + b` won't work but `a.sum("time").drop_extras() + b` will.
|
|
221
240
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Create a model
|
|
2
|
+
|
|
3
|
+
To create a model write:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import pyoframe as pf
|
|
7
|
+
|
|
8
|
+
m = pf.Model()
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Specify a solver
|
|
12
|
+
|
|
13
|
+
By default, Pyoframe will try to use whichever solver is installed on your computer. To specify a particular solver, use the `solver` argument.
|
|
14
|
+
|
|
15
|
+
=== "Gurobi"
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
m = pf.Model(solver="gurobi")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
=== "HiGHS"
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
m = pf.Model(solver="highs")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
=== "COPT"
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
m = pf.Model(solver="copt")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
=== "Ipopt"
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
m = pf.Model(solver="ipopt")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Advanced options
|
|
40
|
+
|
|
41
|
+
Additional options are detailed in the [`Model`][pyoframe.Model] API documentation.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Create constraints
|
|
2
|
+
|
|
3
|
+
Create constraints by using the `<=`, `>=`, and `==` operators between two expressions. For example,
|
|
4
|
+
|
|
5
|
+
<!-- invisible-code-block: python
|
|
6
|
+
import pyoframe as pf
|
|
7
|
+
|
|
8
|
+
m = pf.Model()
|
|
9
|
+
m.Hours_Worked = pf.Variable({"day": ["Mon", "Tue", "Wed", "Thu", "Fri"]})
|
|
10
|
+
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
m.Con_Max_Weekly_Hours = m.Hours_Worked.sum() <= 40
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
!!! tip "Naming constraints"
|
|
18
|
+
I like prefixing constraint names with `Con_` to easily distinguish them from other module attributes.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Handle extra labels
|
|
22
|
+
|
|
23
|
+
When creating constraints, Pyoframe always merges the left- and right-hand side expressions into a single expression (e.g. `a <= b` becomes `(a - b) <= 0`). Thus, if the left- and/or right-hand sides have labels not present in the other side, you will need to handle these extra labels using `drop_extras()` or `keep_extras()`. Read [Addition and its quirks](../concepts/addition.md) to learn more or see the [diet problem](../../examples/diet.md) for an example.
|
|
24
|
+
|
|
25
|
+
## Relax a constraint
|
|
26
|
+
|
|
27
|
+
Refer to the API documentation for [`.relax()`][pyoframe.Constraint.relax].
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Create expressions
|
|
2
|
+
|
|
3
|
+
Mathematical expressions in Pyoframe are represented by the [`Expression`][pyoframe.Expression] class and can be created in a few ways.
|
|
4
|
+
|
|
5
|
+
## Using arithmetic operators
|
|
6
|
+
|
|
7
|
+
Expressions are automatically created whenever standard arithmetic operators (`+`, `-`, `*`, `/`, `**`) are used between Pyoframe objects. For example, the following code creates the expression `m.hours_remaining`:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import pyoframe as pf
|
|
11
|
+
|
|
12
|
+
m = pf.Model()
|
|
13
|
+
m.Hours_Worked = pf.Variable({"day": ["Mon", "Tue", "Wed", "Thu", "Fri"]}, lb=0)
|
|
14
|
+
m.Hours_Sleep = pf.Variable({"day": ["Fri", "Thu", "Wed", "Tue", "Mon"]}, lb=0)
|
|
15
|
+
m.hours_remaining = 24 - m.Hours_Worked - m.Hours_Sleep
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```pycon
|
|
19
|
+
>>> m.hours_remaining
|
|
20
|
+
<Expression (linear) height=5 terms=15>
|
|
21
|
+
┌─────┬───────────────────────────────────────────┐
|
|
22
|
+
│ day ┆ expression │
|
|
23
|
+
│ (5) ┆ │
|
|
24
|
+
╞═════╪═══════════════════════════════════════════╡
|
|
25
|
+
│ Mon ┆ 24 - Hours_Worked[Mon] - Hours_Sleep[Mon] │
|
|
26
|
+
│ Tue ┆ 24 - Hours_Worked[Tue] - Hours_Sleep[Tue] │
|
|
27
|
+
│ Wed ┆ 24 - Hours_Worked[Wed] - Hours_Sleep[Wed] │
|
|
28
|
+
│ Thu ┆ 24 - Hours_Worked[Thu] - Hours_Sleep[Thu] │
|
|
29
|
+
│ Fri ┆ 24 - Hours_Worked[Fri] - Hours_Sleep[Fri] │
|
|
30
|
+
└─────┴───────────────────────────────────────────┘
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
!!! warning "Pyoframe always aligns labels and dimensions"
|
|
35
|
+
|
|
36
|
+
Pyoframe always performs operations label-by-label. For example, Pyoframe subtracted `m.Hours_Sleep` from `m.Hours_Worked` using the labels; the fact that the days were listed in reverse order in `m.Hours_Sleep` (see above) does not matter.
|
|
37
|
+
|
|
38
|
+
When the left- and/or right-hand side expressions have labels not present in the other, it may be necessary to use `.keep_extras()` or `.drop_extras()` to specify how these extra labels should be handled. Similarly, if one of the two operands is missing a dimension, it may be necessary to use `.over` to force broadcasting. Read [Addition and its quirks](../concepts/addition.md) to learn more.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Using parameters
|
|
43
|
+
|
|
44
|
+
External data can be incorporated into an optimization problem by using [`pf.Param(data)`][pyoframe.Param] which converts a DataFrame into a Pyoframe expression. The last column of the DataFrame will be treated as the expression value, and all other columns will be treated as labels. For example, the following code creates a Pyoframe expression equal to `1` on Friday and `0` otherwise.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import pandas as pd
|
|
48
|
+
|
|
49
|
+
is_holiday = pd.DataFrame(
|
|
50
|
+
{"day": ["Mon", "Tue", "Wed", "Thu", "Fri"], "is_holiday": [0, 0, 0, 0, 1]}
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```pycon
|
|
55
|
+
>>> pf.Param(is_holiday)
|
|
56
|
+
<Expression (parameter) height=5 terms=5>
|
|
57
|
+
┌─────┬────────────┐
|
|
58
|
+
│ day ┆ expression │
|
|
59
|
+
│ (5) ┆ │
|
|
60
|
+
╞═════╪════════════╡
|
|
61
|
+
│ Mon ┆ 0 │
|
|
62
|
+
│ Tue ┆ 0 │
|
|
63
|
+
│ Wed ┆ 0 │
|
|
64
|
+
│ Thu ┆ 0 │
|
|
65
|
+
│ Fri ┆ 1 │
|
|
66
|
+
└─────┴────────────┘
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The expression can then be used like any other Pyoframe object:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
m.holiday_hours_worked = m.Hours_Worked * pf.Param(is_holiday)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Note that `pf.Param` is automatically applied when a Pyoframe object is operated with a DataFrame so the previous line can be simplified to
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
m.holiday_hours_worked = m.Hours_Worked * is_holiday
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
!!! tip "`pf.Param` also accepts file paths"
|
|
83
|
+
|
|
84
|
+
`pf.Param` also accepts a file path to a `.csv` or `.parquet` file, see the [`Param`][pyoframe.Param] API documentation to learn more.
|
|
85
|
+
|
|
86
|
+
## Using transforms
|
|
87
|
+
|
|
88
|
+
The functions [`sum`][pyoframe.Expression.sum], [`sum_by`][pyoframe.Expression.sum_by], [`map`][pyoframe.Expression.map], [`next`][pyoframe.Variable.next], [`rolling_sum`][pyoframe.Expression.rolling_sum], and [`within`][pyoframe.Expression.within] are _transforms_ that make it easy to convert an expression or variable from one shape to another. For example, `sum` can be used to collapse a dimensioned expression into a dimensionless one:
|
|
89
|
+
|
|
90
|
+
```pycon
|
|
91
|
+
>>> m.Hours_Worked.sum()
|
|
92
|
+
<Expression (linear) terms=5>
|
|
93
|
+
Hours_Worked[Mon] + Hours_Worked[Tue] + Hours_Worked[Wed] + Hours_Worked[Thu] + Hours_Worked[Fri]
|
|
94
|
+
|
|
95
|
+
```
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Create variables
|
|
2
|
+
|
|
3
|
+
To create a variable, attach it to a `Model`:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import pyoframe as pf
|
|
7
|
+
|
|
8
|
+
m = pf.Model()
|
|
9
|
+
|
|
10
|
+
m.Var_Name = pf.Variable()
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The variable can later be accessed via the model attribute,
|
|
14
|
+
|
|
15
|
+
```pycon
|
|
16
|
+
>>> m.Var_Name
|
|
17
|
+
<Variable 'Var_Name' >
|
|
18
|
+
Var_Name
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
!!! tip "Tip: Use uppercase names for variables"
|
|
23
|
+
|
|
24
|
+
Uppercase names for variables (i.e. `Var_Name` not `var_name`) makes variables (the most important part of your model) easy to distinguish from other model attributes.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Set bounds
|
|
28
|
+
|
|
29
|
+
By default, variables are unbounded. To set a lower or upper bound, use the `lb` or `ub` arguments. For example,
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
m.Positive_Var = pf.Variable(lb=0)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
!!! tip "Bounds can be expressions"
|
|
36
|
+
|
|
37
|
+
`lb` and `ub` accepts fully formed [Pyoframe expressions](./create-expressions.md), not only constants.
|
|
38
|
+
|
|
39
|
+
## Set domain
|
|
40
|
+
|
|
41
|
+
By default, variables are continuous. To create a binary or integer variable use the `vtype` argument:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
m.Binary_Var = pf.Variable(vtype="binary")
|
|
45
|
+
m.Integer_Var = pf.Variable(vtype="integer")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Use dimensions and labels
|
|
49
|
+
|
|
50
|
+
Passing a DataFrame to `pf.Variable` will create a variable for every row in the DataFrame. I call this a _dimensioned variable_.
|
|
51
|
+
|
|
52
|
+
=== "pandas"
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import pandas as pd
|
|
56
|
+
import pyoframe as pf
|
|
57
|
+
|
|
58
|
+
years = pd.DataFrame({"year": [2025, 2026, 2027]})
|
|
59
|
+
|
|
60
|
+
m = pf.Model()
|
|
61
|
+
m.Yearly_Var = pf.Variable(years)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
=== "polars"
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import polars as pl
|
|
68
|
+
import pyoframe as pf
|
|
69
|
+
|
|
70
|
+
years = pl.DataFrame({"year": [2025, 2026, 2027]})
|
|
71
|
+
|
|
72
|
+
m = pf.Model()
|
|
73
|
+
m.Yearly_Var = pf.Variable(years)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Notice how the DataFrame's column name becomes the dimension name and the DataFrame's values become the variable's labels:
|
|
77
|
+
|
|
78
|
+
```pycon
|
|
79
|
+
>>> m.Yearly_Var
|
|
80
|
+
<Variable 'Yearly_Var' height=3>
|
|
81
|
+
┌──────┬──────────────────┐
|
|
82
|
+
│ year ┆ variable │
|
|
83
|
+
│ (3) ┆ │
|
|
84
|
+
╞══════╪══════════════════╡
|
|
85
|
+
│ 2025 ┆ Yearly_Var[2025] │
|
|
86
|
+
│ 2026 ┆ Yearly_Var[2026] │
|
|
87
|
+
│ 2027 ┆ Yearly_Var[2027] │
|
|
88
|
+
└──────┴──────────────────┘
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
!!! warning "Labels must be unique"
|
|
93
|
+
|
|
94
|
+
An error will be raised if the input DataFrame contains duplicate rows since every variable must have its own unique label.
|
|
95
|
+
|
|
96
|
+
### Combine dimensions
|
|
97
|
+
|
|
98
|
+
Passing multiple DataFrames to `pf.Variable()` will create a variable for every row in the [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product) of the DataFrames.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
=== "pandas"
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
years = pd.DataFrame({"year": [2025, 2026, 2027]})
|
|
105
|
+
locations = pd.DataFrame({"city": ["Toronto", "Mexico City"]})
|
|
106
|
+
|
|
107
|
+
m = pf.Model()
|
|
108
|
+
m.Cartesian_Var = pf.Variable(years, locations)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
=== "polars"
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
years = pl.DataFrame({"year": [2025, 2026, 2027]})
|
|
115
|
+
locations = pl.DataFrame({"city": ["Toronto", "Mexico City"]})
|
|
116
|
+
|
|
117
|
+
m = pf.Model()
|
|
118
|
+
m.Cartesian_Var = pf.Variable(years, locations)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```pycon
|
|
122
|
+
>>> m.Cartesian_Var
|
|
123
|
+
<Variable 'Cartesian_Var' height=6>
|
|
124
|
+
┌──────┬─────────────┬─────────────────────────────────┐
|
|
125
|
+
│ year ┆ city ┆ variable │
|
|
126
|
+
│ (3) ┆ (2) ┆ │
|
|
127
|
+
╞══════╪═════════════╪═════════════════════════════════╡
|
|
128
|
+
│ 2025 ┆ Toronto ┆ Cartesian_Var[2025,Toronto] │
|
|
129
|
+
│ 2025 ┆ Mexico City ┆ Cartesian_Var[2025,Mexico_City] │
|
|
130
|
+
│ 2026 ┆ Toronto ┆ Cartesian_Var[2026,Toronto] │
|
|
131
|
+
│ 2026 ┆ Mexico City ┆ Cartesian_Var[2026,Mexico_City] │
|
|
132
|
+
│ 2027 ┆ Toronto ┆ Cartesian_Var[2027,Toronto] │
|
|
133
|
+
│ 2027 ┆ Mexico City ┆ Cartesian_Var[2027,Mexico_City] │
|
|
134
|
+
└──────┴─────────────┴─────────────────────────────────┘
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
!!! tip "Use a multi-column DataFrame to create sparse variables"
|
|
139
|
+
|
|
140
|
+
An alternative way to create a variable with multiple dimensions (e.g. `year` and `city`) is to pass a single DataFrame with multiple columns to `pf.Variable`. This approach lets you control exactly which rows to include, allowing for sparsely populated variables instead of the cartesian product.
|
|
141
|
+
|
|
142
|
+
### Other approaches
|
|
143
|
+
|
|
144
|
+
<!-- invisible-code-block: python
|
|
145
|
+
years = pl.DataFrame({"year": [2025, 2026, 2027]})
|
|
146
|
+
|
|
147
|
+
m = pf.Model()
|
|
148
|
+
m.Yearly_Var = pf.Variable(years)
|
|
149
|
+
-->
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
DataFrames are not the only way to create a dimensioned variable. In the following examples, all the `m.Yearly_Var` are equivalent.
|
|
153
|
+
|
|
154
|
+
=== "Pyoframe sets"
|
|
155
|
+
|
|
156
|
+
Pyoframe offers a [`Set`][pyoframe.Set] class to easily define dimensioned variables in a reusable way.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
years = pf.Set(year=[2025, 2026, 2027]) # define once, reuse for multiple variables
|
|
160
|
+
m.Yearly_Var_1 = pf.Variable(years)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
=== "Dictionaries"
|
|
164
|
+
|
|
165
|
+
Dictionaries are shortcuts for writing `pf.Variable(pl.DataFrame(dict_data))`.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
m.Yearly_Var_2 = pf.Variable({"year": [2025, 2026, 2027]})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
=== "Other Pyoframe objects"
|
|
172
|
+
|
|
173
|
+
Passing a Pyoframe object such as an expression or another variable to `pf.Variable()` will create a variable with the same labels as the object.
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
m.Yearly_Var_3 = pf.Variable(
|
|
177
|
+
m.Yearly_Var
|
|
178
|
+
) # Creates a variable with the same labels as m.Yearly_Var
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
=== "Series or indexes"
|
|
182
|
+
|
|
183
|
+
A pandas `Index` or `Series` (or a polars `Series`) is treated as a DataFrame.
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
years = pd.Series([2025, 2026, 2027], name="year")
|
|
187
|
+
m.Yearly_Var_4 = pf.Variable(years)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
years = pd.Index([2025, 2026, 2027], name="year")
|
|
192
|
+
m.Yearly_Var_5 = pf.Variable(years)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
<!-- invisible-code-block: python
|
|
200
|
+
from polars.testing import assert_frame_equal
|
|
201
|
+
|
|
202
|
+
for con in [
|
|
203
|
+
m.Yearly_Var,
|
|
204
|
+
m.Yearly_Var_1,
|
|
205
|
+
m.Yearly_Var_2,
|
|
206
|
+
m.Yearly_Var_3,
|
|
207
|
+
m.Yearly_Var_4,
|
|
208
|
+
m.Yearly_Var_5,
|
|
209
|
+
]:
|
|
210
|
+
assert "year" in con.data.columns
|
|
211
|
+
assert len(con.data) == 3
|
|
212
|
+
|
|
213
|
+
-->
|
|
214
|
+
|
|
215
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Define an objective
|
|
2
|
+
|
|
3
|
+
To set an objective for your optimization problem, assign an expression to either the `.minimize` or `.maximize` attribute of the Model. For example:
|
|
4
|
+
|
|
5
|
+
<!-- invisible-code-block: python
|
|
6
|
+
import pyoframe as pf
|
|
7
|
+
|
|
8
|
+
m = pf.Model()
|
|
9
|
+
capital_costs = pf.Expression.constant(3)
|
|
10
|
+
operating_costs = pf.Expression.constant(3)
|
|
11
|
+
|
|
12
|
+
-->
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
m.minimize = capital_costs + operating_costs
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Note that the objective expression must be dimensionless (it makes no sense to have multiple objectives with different labels). You can use [`.sum()`](./create-expressions.md) to collapse a dimensioned expression into a dimensionless one.
|
|
19
|
+
|
|
20
|
+
## Define an objective incrementally
|
|
21
|
+
|
|
22
|
+
For larger models, it is often convenient to define the objective function incrementally. To do so, use the `+=` operator:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
m = pf.Model()
|
|
26
|
+
m.minimize = 0
|
|
27
|
+
|
|
28
|
+
# Later in your code
|
|
29
|
+
m.minimize += capital_costs
|
|
30
|
+
|
|
31
|
+
# Somewhere else in your code
|
|
32
|
+
m.minimize += operating_costs
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Alternative approach
|
|
36
|
+
|
|
37
|
+
Alternatively, rather than use `.minimize` or `.maximize`, you can use `.objective` and define the direction using the `sense` argument during model creation:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
m = pf.Model(sense="min")
|
|
41
|
+
m.objective = capital_costs + operating_costs
|
|
42
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Read results
|
|
2
|
+
|
|
3
|
+
Use [`.solution`][pyoframe.Variable.solution] to read the optimal values of Variables after optimization (e.g. `m.Hours_Worked.solution`). For dimensioned variables, `.solution` returns a polars DataFrame.
|
|
4
|
+
|
|
5
|
+
Similarly, use [`.dual`][pyoframe.Constraint.dual] to read the dual values (aka. shadow prices) of Constraints (e.g. `m.Con_Max_Weekly_Hours.dual`).
|
|
6
|
+
|
|
7
|
+
You can also output your model problem or solution using [`.write(…)`][pyoframe.Model.write].
|
|
8
|
+
|
|
9
|
+
!!! info "Returning Pandas DataFrames"
|
|
10
|
+
|
|
11
|
+
Pyoframe currently always returns Polars DataFrames but you can easily convert them to Pandas using [`.to_pandas()`](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.to_pandas.html#polars.DataFrame.to_pandas). In the future, we plan to add support for automatically returning Pandas DataFrames. [Upvote the issue](https://github.com/Bravos-Power/pyoframe/issues/47) if you'd like this feature.
|
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Run and configure the solver
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
To run your optimization model call,
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<!-- invisible-code-block: python
|
|
6
|
+
import pyoframe as pf
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
m = pf.Model()
|
|
9
|
+
-->
|
|
8
10
|
|
|
9
11
|
```python
|
|
10
|
-
m
|
|
11
|
-
m.attr.Silent = True
|
|
12
|
+
m.optimize()
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```pycon
|
|
17
|
-
>>> m.optimize()
|
|
18
|
-
>>> m.attr.TerminationStatus # PyOptInterface attribute (always available)
|
|
19
|
-
<TerminationStatusCode.OPTIMAL: 2>
|
|
20
|
-
>>> m.attr.Status # Gurobi attribute (only available with Gurobi)
|
|
21
|
-
2
|
|
22
|
-
|
|
23
|
-
```
|
|
15
|
+
Recall that you chose your solver upon [creating your model](./create-a-model.md#specify-a-solver).
|
|
24
16
|
|
|
25
|
-
##
|
|
17
|
+
## Configure solver parameters
|
|
26
18
|
|
|
27
19
|
Every solver has a set of parameters that you can read or set using `model.params.<your-param>`.
|
|
28
20
|
|
|
@@ -70,7 +62,29 @@ Every solver has a set of parameters that you can read or set using `model.param
|
|
|
70
62
|
Ipopt does not support reading parameters (only setting them).
|
|
71
63
|
|
|
72
64
|
|
|
73
|
-
##
|
|
65
|
+
## Configure solver attributes
|
|
66
|
+
|
|
67
|
+
Pyoframe lets you read and set solver attributes using `model.attr.<your-attribute>`. For example, if you'd like to prevent the solver from printing to the console you can do:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
m = pf.Model()
|
|
71
|
+
m.attr.Silent = True
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Pyoframe supports a set of [standard attributes](https://metab0t.github.io/PyOptInterface/model.html#id1) as well as additional [Gurobi attributes](https://docs.gurobi.com/projects/optimizer/en/current/reference/attributes/model.html) and [COPT attributes](https://guide.coap.online/copt/en-doc/attribute.html).
|
|
75
|
+
|
|
76
|
+
```pycon
|
|
77
|
+
>>> m.optimize()
|
|
78
|
+
>>> m.attr.TerminationStatus # PyOptInterface attribute (always available)
|
|
79
|
+
<TerminationStatusCode.OPTIMAL: 2>
|
|
80
|
+
>>> m.attr.Status # Gurobi attribute (only available with Gurobi)
|
|
81
|
+
2
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
### Variable and constraint attributes
|
|
74
88
|
|
|
75
89
|
Similar to above, Pyoframe allows directly accessing the PyOptInterface or the solver's variable and constraint attributes.
|
|
76
90
|
|
|
@@ -82,7 +96,7 @@ m.X.attr.PrimalStart = 5 # Set initial value for warm start
|
|
|
82
96
|
|
|
83
97
|
If the variable or constraint is dimensioned, the attribute can accept/return a DataFrame instead of a constant.
|
|
84
98
|
|
|
85
|
-
##
|
|
99
|
+
## Configure an advanced license
|
|
86
100
|
|
|
87
101
|
Both COPT and Gurobi support advanced license configurations through the `solver_env` parameter:
|
|
88
102
|
|
{pyoframe-1.1.0 → pyoframe-1.2.1}/docs/learn/get-started/basic-example/example-with-dimensions.md
RENAMED
|
@@ -114,7 +114,7 @@ First, multiply the variable by the protein amount.
|
|
|
114
114
|
|
|
115
115
|
As you can see, Pyoframe with a bit of magic converted the `Variable` into an `Expression` where the coefficients are the protein amounts.
|
|
116
116
|
|
|
117
|
-
*[with a bit of magic]: Pyoframe always converts DataFrames into Expressions by taking the first columns as dimensions and the last column as values. Additionally,
|
|
117
|
+
*[with a bit of magic]: Pyoframe always converts DataFrames into Expressions by taking the first columns as dimensions and the last column as values. Additionally, operations are always compute between values with the same labels.
|
|
118
118
|
|
|
119
119
|
Second, notice that the `Expression` still has the `food` dimension—it really contains two separate expressions, one for tofu and one for chickpeas. All objective functions must be a single expression (without dimensions) so let's sum over the `food` dimension.
|
|
120
120
|
|