pyoframe 1.1.0__tar.gz → 1.2.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.
Files changed (262) hide show
  1. {pyoframe-1.1.0 → pyoframe-1.2.0}/PKG-INFO +1 -1
  2. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/.nav.yml +1 -1
  3. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/contribute/index.md +1 -1
  4. {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.0/docs/learn/concepts}/.nav.yml +1 -1
  5. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/concepts/addition.md +32 -13
  6. pyoframe-1.2.0/docs/learn/develop/.nav.yml +8 -0
  7. pyoframe-1.2.0/docs/learn/develop/create-a-model.md +41 -0
  8. pyoframe-1.2.0/docs/learn/develop/create-constraints.md +27 -0
  9. pyoframe-1.2.0/docs/learn/develop/create-expressions.md +95 -0
  10. pyoframe-1.2.0/docs/learn/develop/create-variables.md +215 -0
  11. pyoframe-1.2.0/docs/learn/develop/define-objective.md +42 -0
  12. pyoframe-1.2.0/docs/learn/develop/read-results.md +11 -0
  13. pyoframe-1.1.0/docs/learn/concepts/solver-access.md → pyoframe-1.2.0/docs/learn/develop/run-and-configure-the-solver.md +33 -19
  14. pyoframe-1.2.0/docs/learn/get-started/.nav.yml +4 -0
  15. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/basic-example/example-with-dimensions.md +1 -1
  16. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/installation.md +2 -2
  17. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/migrate/v1.0.md +2 -2
  18. {pyoframe-1.1.0 → pyoframe-1.2.0}/pyoframe.egg-info/PKG-INFO +1 -1
  19. {pyoframe-1.1.0 → pyoframe-1.2.0}/pyoframe.egg-info/SOURCES.txt +12 -9
  20. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_arithmetic.py +3 -2
  21. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_core.py +21 -1
  22. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_model.py +178 -16
  23. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_version.py +3 -3
  24. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_addition.py +53 -2
  25. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_model.py +10 -0
  26. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_names.py +16 -0
  27. pyoframe-1.2.0/tests/test_objective.py +90 -0
  28. pyoframe-1.1.0/docs/learn/advanced-concepts/datastructure.md +0 -7
  29. pyoframe-1.1.0/docs/learn/concepts/.nav.yml +0 -4
  30. pyoframe-1.1.0/docs/learn/concepts/special-functions.md +0 -12
  31. pyoframe-1.1.0/docs/learn/get-started/.nav.yml +0 -5
  32. pyoframe-1.1.0/docs/learn/get-started/basics.md +0 -347
  33. pyoframe-1.1.0/tests/test_objective.py +0 -22
  34. {pyoframe-1.1.0 → pyoframe-1.2.0}/.gitattributes +0 -0
  35. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/CODEOWNERS +0 -0
  36. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/actions/setup_optimizers_linux/action.yml +0 -0
  37. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/actions/setup_optimizers_macos/action.yml +0 -0
  38. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/actions/setup_optimizers_windows/action.yml +0 -0
  39. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/dependabot.yml +0 -0
  40. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/ci.yml +0 -0
  41. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/format.yml +0 -0
  42. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/lines_changed_counter.yml +0 -0
  43. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/lint.yml +0 -0
  44. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/publish_doc.yml +0 -0
  45. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/publish_doc_dev.yml +0 -0
  46. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/publish_to_pypi.yml +0 -0
  47. {pyoframe-1.1.0 → pyoframe-1.2.0}/.github/workflows/test_doc.yml +0 -0
  48. {pyoframe-1.1.0 → pyoframe-1.2.0}/.gitignore +0 -0
  49. {pyoframe-1.1.0 → pyoframe-1.2.0}/.pre-commit-config.yaml +0 -0
  50. {pyoframe-1.1.0 → pyoframe-1.2.0}/.vscode/launch.json +0 -0
  51. {pyoframe-1.1.0 → pyoframe-1.2.0}/.vscode/settings.json +0 -0
  52. {pyoframe-1.1.0 → pyoframe-1.2.0}/CHANGELOG.md +0 -0
  53. {pyoframe-1.1.0 → pyoframe-1.2.0}/LICENSE +0 -0
  54. {pyoframe-1.1.0 → pyoframe-1.2.0}/README.md +0 -0
  55. {pyoframe-1.1.0 → pyoframe-1.2.0}/conftest.py +0 -0
  56. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/examples/.nav.yml +0 -0
  57. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/examples/diet.md +0 -0
  58. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/examples/facility_location.md +0 -0
  59. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/examples/index.md +0 -0
  60. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/examples/portfolio_optimization.md +0 -0
  61. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/examples/production.md +0 -0
  62. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/index.md +0 -0
  63. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/javascripts/feedback.js +0 -0
  64. {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.0/docs/learn/concepts}/internals.md +0 -0
  65. {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.0/docs/learn/concepts}/performance.md +0 -0
  66. {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.0/docs/learn/concepts}/quadratics.md +0 -0
  67. {pyoframe-1.1.0/docs/learn/advanced-concepts → pyoframe-1.2.0/docs/learn/concepts}/troubleshooting.md +0 -0
  68. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/basic-example/example.md +0 -0
  69. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/basic-example/food_data.csv +0 -0
  70. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/basic-example/foods.csv +0 -0
  71. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/basic-example/foods_to_nutrients.csv +0 -0
  72. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/basic-example/nutrients.csv +0 -0
  73. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/basic-example/results.csv +0 -0
  74. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/power_grid_example.ipynb +0 -0
  75. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/learn/get-started/three-bus.png +0 -0
  76. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/overrides/main.html +0 -0
  77. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/overrides/partials/actions.html +0 -0
  78. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/overrides/partials/comments.html +0 -0
  79. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/overrides/partials/integrations/analytics/custom.html +0 -0
  80. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/.nav.yml +0 -0
  81. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/bases/.nav.yml +0 -0
  82. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/bases/BaseBlock.md +0 -0
  83. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/bases/BaseOperableBlock.md +0 -0
  84. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/index.md +0 -0
  85. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/public/.nav.yml +0 -0
  86. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/public/Config.md +0 -0
  87. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/types/.nav.yml +0 -0
  88. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/reference/types/Operable.md +0 -0
  89. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/stylesheets/extra.css +0 -0
  90. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/why-pyoframe/data_py.parquet +0 -0
  91. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/why-pyoframe/gen_py.parquet +0 -0
  92. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/why-pyoframe/index.md +0 -0
  93. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/why-pyoframe/pyoframe-performance.ipynb +0 -0
  94. {pyoframe-1.1.0 → pyoframe-1.2.0}/docs/why-pyoframe/three-bus-four-gen.png +0 -0
  95. {pyoframe-1.1.0 → pyoframe-1.2.0}/giscus.json +0 -0
  96. {pyoframe-1.1.0 → pyoframe-1.2.0}/mkdocs.yml +0 -0
  97. {pyoframe-1.1.0 → pyoframe-1.2.0}/pyoframe.egg-info/dependency_links.txt +0 -0
  98. {pyoframe-1.1.0 → pyoframe-1.2.0}/pyoframe.egg-info/requires.txt +0 -0
  99. {pyoframe-1.1.0 → pyoframe-1.2.0}/pyoframe.egg-info/top_level.txt +0 -0
  100. {pyoframe-1.1.0 → pyoframe-1.2.0}/pyproject.toml +0 -0
  101. {pyoframe-1.1.0 → pyoframe-1.2.0}/scripts/archive/benchmark_assign_ids_constraints.py +0 -0
  102. {pyoframe-1.1.0 → pyoframe-1.2.0}/scripts/archive/benchmark_assign_ids_constraints_2.py +0 -0
  103. {pyoframe-1.1.0 → pyoframe-1.2.0}/scripts/archive/benchmark_assign_ids_variables.py +0 -0
  104. {pyoframe-1.1.0 → pyoframe-1.2.0}/scripts/archive/benchmark_attr_performance.py +0 -0
  105. {pyoframe-1.1.0 → pyoframe-1.2.0}/scripts/generate_api_reference.py +0 -0
  106. {pyoframe-1.1.0 → pyoframe-1.2.0}/scripts/griffe_extensions.py +0 -0
  107. {pyoframe-1.1.0 → pyoframe-1.2.0}/setup.cfg +0 -0
  108. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/__init__.py +0 -0
  109. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_constants.py +0 -0
  110. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_model_element.py +0 -0
  111. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_monkey_patch.py +0 -0
  112. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_objective.py +0 -0
  113. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_param.py +0 -0
  114. {pyoframe-1.1.0 → pyoframe-1.2.0}/src/pyoframe/_utils.py +0 -0
  115. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/__init__.py +0 -0
  116. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/conftest.py +0 -0
  117. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/README.md +0 -0
  118. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/__init__.py +0 -0
  119. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/__init__.py +0 -0
  120. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/input_data/orders.csv +0 -0
  121. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/input_data/parameters.csv +0 -0
  122. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/model.py +0 -0
  123. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/results/objective.csv +0 -0
  124. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/results/problem-copt-machine.lp +0 -0
  125. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/results/problem-copt-pretty.lp +0 -0
  126. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/results/problem-gurobi-machine.lp +0 -0
  127. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/results/problem-gurobi-pretty.lp +0 -0
  128. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/results/problem-highs-machine.lp +0 -0
  129. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/cutting_stock_problem/results/problem-highs-pretty.lp +0 -0
  130. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/README.md +0 -0
  131. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/__init__.py +0 -0
  132. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/input_data/foods.csv +0 -0
  133. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/input_data/foods_to_nutrients.csv +0 -0
  134. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/input_data/nutrients.csv +0 -0
  135. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/model.py +0 -0
  136. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/model_gurobipy.py +0 -0
  137. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/Buy.csv +0 -0
  138. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/Buy_ub.csv +0 -0
  139. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/max_nutrients.csv +0 -0
  140. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/min_nutrients.csv +0 -0
  141. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/objective.csv +0 -0
  142. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/problem-copt-machine.lp +0 -0
  143. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/problem-copt-pretty.lp +0 -0
  144. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/problem-gurobi-machine.lp +0 -0
  145. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/problem-gurobi-pretty.lp +0 -0
  146. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/problem-highs-machine.lp +0 -0
  147. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/problem-highs-pretty.lp +0 -0
  148. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/solution-copt-machine.sol +0 -0
  149. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/solution-copt-pretty.sol +0 -0
  150. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/solution-gurobi-machine.sol +0 -0
  151. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/solution-gurobi-pretty.sol +0 -0
  152. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/solution-highs-machine.sol +0 -0
  153. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/diet_problem/results/solution-highs-pretty.sol +0 -0
  154. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_location/__init__.py +0 -0
  155. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_location/model.py +0 -0
  156. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_location/results/objective.csv +0 -0
  157. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_location/results/problem-gurobi-machine.lp +0 -0
  158. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_location/results/problem-gurobi-pretty.lp +0 -0
  159. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/__init__.py +0 -0
  160. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/input_data/plants.csv +0 -0
  161. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/input_data/transport_costs.csv +0 -0
  162. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/input_data/wharehouses.csv +0 -0
  163. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/model.py +0 -0
  164. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/model_gurobipy.py +0 -0
  165. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/objective.csv +0 -0
  166. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/open.csv +0 -0
  167. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/problem-copt-machine.lp +0 -0
  168. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/problem-copt-pretty.lp +0 -0
  169. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/problem-gurobi-machine.lp +0 -0
  170. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/problem-gurobi-pretty.lp +0 -0
  171. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/problem-highs-machine.lp +0 -0
  172. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/problem-highs-pretty.lp +0 -0
  173. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/solution-copt-machine.sol +0 -0
  174. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/solution-copt-pretty.sol +0 -0
  175. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/solution-gurobi-machine.sol +0 -0
  176. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/solution-gurobi-pretty.sol +0 -0
  177. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/solution-highs-machine.sol +0 -0
  178. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/solution-highs-pretty.sol +0 -0
  179. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/facility_problem/results/transport.csv +0 -0
  180. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/input_data/assets.csv +0 -0
  181. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/input_data/covariance.csv +0 -0
  182. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/input_data/portfolio_params.csv +0 -0
  183. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/model.py +0 -0
  184. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/con_min_return.csv +0 -0
  185. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/con_weights_sum.csv +0 -0
  186. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/objective.csv +0 -0
  187. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/problem-copt-machine.lp +0 -0
  188. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/problem-copt-pretty.lp +0 -0
  189. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/problem-gurobi-machine.lp +0 -0
  190. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/problem-gurobi-pretty.lp +0 -0
  191. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/problem-highs-machine.lp +0 -0
  192. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/problem-highs-pretty.lp +0 -0
  193. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/solution-copt-machine.sol +0 -0
  194. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/solution-copt-pretty.sol +0 -0
  195. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/solution-gurobi-machine.sol +0 -0
  196. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/solution-gurobi-pretty.sol +0 -0
  197. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/solution-highs-machine.sol +0 -0
  198. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/solution-highs-pretty.sol +0 -0
  199. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/portfolio_optim/results/weight.csv +0 -0
  200. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/__init__.py +0 -0
  201. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/input_data/machines_availability.csv +0 -0
  202. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/input_data/processing_times.csv +0 -0
  203. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/input_data/products_profit.csv +0 -0
  204. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/model.py +0 -0
  205. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/objective.csv +0 -0
  206. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/problem-copt-machine.lp +0 -0
  207. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/problem-copt-pretty.lp +0 -0
  208. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/problem-gurobi-machine.lp +0 -0
  209. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/problem-gurobi-pretty.lp +0 -0
  210. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/problem-highs-machine.lp +0 -0
  211. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/problem-highs-pretty.lp +0 -0
  212. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/solution-copt-machine.sol +0 -0
  213. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/solution-copt-pretty.sol +0 -0
  214. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/solution-gurobi-machine.sol +0 -0
  215. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/solution-gurobi-pretty.sol +0 -0
  216. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/solution-highs-machine.sol +0 -0
  217. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/solution-highs-pretty.sol +0 -0
  218. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/production_planning/results/solution.csv +0 -0
  219. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/README.md +0 -0
  220. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/__init__.py +0 -0
  221. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/input_data/elspot-prices_2021_hourly_eur.csv +0 -0
  222. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/model.py +0 -0
  223. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/Pump.csv +0 -0
  224. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/Storage_level.csv +0 -0
  225. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/Turb.csv +0 -0
  226. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/objective.csv +0 -0
  227. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/problem-copt-machine.lp +0 -0
  228. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/problem-copt-pretty.lp +0 -0
  229. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/problem-gurobi-machine.lp +0 -0
  230. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/problem-gurobi-pretty.lp +0 -0
  231. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/problem-highs-machine.lp +0 -0
  232. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/problem-highs-pretty.lp +0 -0
  233. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/solution-copt-machine.sol +0 -0
  234. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/solution-copt-pretty.sol +0 -0
  235. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/solution-gurobi-machine.sol +0 -0
  236. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/solution-gurobi-pretty.sol +0 -0
  237. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/solution-highs-machine.sol +0 -0
  238. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/pumped_storage/results/solution-highs-pretty.sol +0 -0
  239. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/__init__.py +0 -0
  240. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/input_data/initial_numbers.csv +0 -0
  241. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/model.py +0 -0
  242. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/problem-copt-machine.lp +0 -0
  243. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/problem-copt-pretty.lp +0 -0
  244. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/problem-gurobi-machine.lp +0 -0
  245. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/problem-gurobi-pretty.lp +0 -0
  246. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/problem-highs-machine.lp +0 -0
  247. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/problem-highs-pretty.lp +0 -0
  248. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/solution-copt-machine.sol +0 -0
  249. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/solution-copt-pretty.sol +0 -0
  250. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/solution-gurobi-machine.sol +0 -0
  251. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/solution-gurobi-pretty.sol +0 -0
  252. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/solution-highs-machine.sol +0 -0
  253. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/solution-highs-pretty.sol +0 -0
  254. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/examples/sudoku/results/solution.csv +0 -0
  255. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_arithmetic.py +0 -0
  256. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_constraint.py +0 -0
  257. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_examples.py +0 -0
  258. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_io.py +0 -0
  259. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_param.py +0 -0
  260. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_solver.py +0 -0
  261. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/test_variable.py +0 -0
  262. {pyoframe-1.1.0 → pyoframe-1.2.0}/tests/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyoframe
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Blazing fast linear program interface
5
5
  Author-email: Bravos Power <dev@bravospower.com>
6
6
  License-Expression: MIT
@@ -4,8 +4,8 @@ nav:
4
4
  - Home: index.md
5
5
  - Learn:
6
6
  - learn/get-started
7
+ - learn/develop
7
8
  - learn/concepts
8
- - learn/advanced-concepts
9
9
  - learn/migrate
10
10
  - examples
11
11
  - Reference API: reference
@@ -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/advanced-concepts/internals.md) page for some background on how Pyoframe works.
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
 
@@ -1,5 +1,5 @@
1
1
  nav:
2
- - datastructure.md
2
+ - addition.md
3
3
  - performance.md
4
4
  - quadratics.md
5
5
  - troubleshooting.md
@@ -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
- Before delving into these addition modifiers, please note that **these addition rules 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.
9
+ !!! warning "Addition modifiers also apply to subtraction and constraint creation"
10
10
 
11
- The rest of the page is organized as follows:
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
- 1. [The `.over(…)` addition modifier](#adding-expressions-with-differing-dimensions-using-over)
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 with `.over(…)`
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
- ## Handling 'extra' labels with `.keep_extras()` and `.drop_extras()`
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
- ## Order of operations for addition modifiers
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,8 @@
1
+ nav:
2
+ - create-a-model.md
3
+ - create-variables.md
4
+ - create-expressions.md
5
+ - create-constraints.md
6
+ - define-objective.md
7
+ - run-and-configure-the-solver.md
8
+ - read-results.md
@@ -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
- # Solver interface
1
+ # Run and configure the solver
2
2
 
3
- Pyoframe provides a friendly API that allows you to read and set the various attributes and parameters your solver has to offer.
3
+ To run your optimization model call,
4
4
 
5
- ## Model attributes
5
+ <!-- invisible-code-block: python
6
+ import pyoframe as pf
6
7
 
7
- 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:
8
+ m = pf.Model()
9
+ -->
8
10
 
9
11
  ```python
10
- m = pf.Model()
11
- m.attr.Silent = True
12
+ m.optimize()
12
13
  ```
13
14
 
14
- 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).
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
- ## Model parameters
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
- ## Variable and constraint attributes
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
- ## License configuration (COPT and Gurobi)
99
+ ## Configure an advanced license
86
100
 
87
101
  Both COPT and Gurobi support advanced license configurations through the `solver_env` parameter:
88
102
 
@@ -0,0 +1,4 @@
1
+ nav:
2
+ - installation.md
3
+ - basic-example/example.md
4
+ - basic-example/example-with-dimensions.md
@@ -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, multiplication is always done between elements with the same dimensions.
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
 
@@ -50,7 +50,7 @@ Select your chosen solver and follow the installation instructions.
50
50
  To install Gurobi:
51
51
 
52
52
  1. [Download Gurobi](https://www.gurobi.com/downloads/gurobi-software/) from their website (login required) and follow the installation instructions.
53
- 2. Ensure you have a valid Gurobi license installed on your machine. (If you're using Gurobi Compute Server or other atypical licensing setups, refer to [License configuration](../concepts/solver-access.md#license-configuration-copt-and-gurobi).)
53
+ 2. Ensure you have a valid Gurobi license installed on your machine. (If you're using Gurobi Compute Server or other atypical licensing setups, refer to [License configuration](../develop/run-and-configure-the-solver.md#configure-an-advanced-license).)
54
54
 
55
55
  !!! warning "Do not install Gurobi using `pip`"
56
56
 
@@ -61,7 +61,7 @@ Select your chosen solver and follow the installation instructions.
61
61
  To install COPT:
62
62
 
63
63
  1. Download COPT from the e-mail you received after [requesting a license](https://www.cardopt.com/copt) and follow the installation instructions.
64
- 2. Ensure you have valid COPT license files on your machine. (If you're using floating, cluster, or web licenses refer to [License configuration](../concepts/solver-access.md#license-configuration-copt-and-gurobi).)
64
+ 2. Ensure you have valid COPT license files on your machine. (If you're using floating, cluster, or web licenses refer to [License configuration](../develop/run-and-configure-the-solver.md#configure-an-advanced-license).)
65
65
  3. Set the `COPT_HOME` environment variable to point to your COPT installation directory.
66
66
 
67
67
  === "Ipopt"