gpkit-core 0.3.2__tar.gz → 0.3.4__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 (248) hide show
  1. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/PKG-INFO +1 -1
  2. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/__init__.py +1 -1
  3. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/model.py +40 -2
  4. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/core.py +6 -1
  5. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/report.py +214 -40
  6. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_report.py +30 -0
  7. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_report_descriptions.py +18 -8
  8. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.flake8 +0 -0
  9. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.github/workflows/lint.yml +0 -0
  10. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.github/workflows/publish.yml +0 -0
  11. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.github/workflows/pylint.yml +0 -0
  12. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.github/workflows/tests.yml +0 -0
  13. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.gitignore +0 -0
  14. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.importlinter +0 -0
  15. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.pre-commit-config.yaml +0 -0
  16. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.pylintrc +0 -0
  17. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/.pylintrc.examples +0 -0
  18. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/CONTRIBUTING.md +0 -0
  19. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/LICENSE +0 -0
  20. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/Makefile +0 -0
  21. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/README.md +0 -0
  22. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/catalog.toml +0 -0
  23. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/Makefile +0 -0
  24. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/apidoc.sh +0 -0
  25. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/make.bat +0 -0
  26. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/refresh_build.sh +0 -0
  27. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/_static/css/custom.css +0 -0
  28. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/acknowledgements.rst +0 -0
  29. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/advancedcommands.rst +0 -0
  30. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/autodoc/gpkit.constraints.rst +0 -0
  31. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/autodoc/gpkit.interactive.rst +0 -0
  32. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/autodoc/gpkit.nomials.rst +0 -0
  33. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/autodoc/gpkit.rst +0 -0
  34. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/autodoc/gpkit.solvers.rst +0 -0
  35. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/autodoc/gpkit.tools.rst +0 -0
  36. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/autodoc/modules.rst +0 -0
  37. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/citinggpkit.rst +0 -0
  38. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/conf.py +0 -0
  39. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/debugging.rst +0 -0
  40. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/autosweep_output.txt +0 -0
  41. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/beam.svg +0 -0
  42. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/beam_output.txt +0 -0
  43. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/bemt_hover_output.txt +0 -0
  44. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/boundschecking_output.txt +0 -0
  45. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/breakdowns/solartest.py +0 -0
  46. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/breakdowns_output.txt +0 -0
  47. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/checking_result_changes_output.txt +0 -0
  48. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/debug_output.txt +0 -0
  49. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/evaluated_fixed_variables_output.txt +0 -0
  50. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/evaluated_free_variables_output.txt +0 -0
  51. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/external_constraint_output.txt +0 -0
  52. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/external_function_output.txt +0 -0
  53. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/external_sp_output.txt +0 -0
  54. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/freeing_fixed_variables_output.txt +0 -0
  55. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/fuel_burn_output.txt +0 -0
  56. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/gettingstarted_output.txt +0 -0
  57. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/gp_textbook_output.txt +0 -0
  58. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/issue_1513_output.txt +0 -0
  59. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/issue_1522_output.txt +0 -0
  60. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/last_verified.pkl +0 -0
  61. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/last_verified.sol +0 -0
  62. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/loose_constraintsets_output.txt +0 -0
  63. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/migp_output.txt +0 -0
  64. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/model_var_access_output.txt +0 -0
  65. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/performance_modeling_output.txt +0 -0
  66. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/plot_autosweep1d.png +0 -0
  67. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/plot_sweep1d.png +0 -0
  68. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/plot_sweep1d_output.txt +0 -0
  69. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/primal_infeasible_ex1_output.txt +0 -0
  70. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/primal_infeasible_ex2_output.txt +0 -0
  71. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/relaxation_output.txt +0 -0
  72. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/simple_box_output.txt +0 -0
  73. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/simple_sp_output.txt +0 -0
  74. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/simpleflight_output.txt +0 -0
  75. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/sin_approx_example_output.txt +0 -0
  76. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/solar.p +0 -0
  77. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/solar_10.p +0 -0
  78. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/solar_12.p +0 -0
  79. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/solar_13.p +0 -0
  80. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/sp_to_gp_sweep_output.txt +0 -0
  81. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/sub_multi_values_output.txt +0 -0
  82. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/substitutions_output.txt +0 -0
  83. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/tight_constraintsets_output.txt +0 -0
  84. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/toml/beam.toml +0 -0
  85. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/toml/modular_aircraft.toml +0 -0
  86. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/toml/performance_modeling.toml +0 -0
  87. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/toml/simple_box.toml +0 -0
  88. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/toml/simpleflight.toml +0 -0
  89. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/toml/water_tank.toml +0 -0
  90. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/toml/wing_aircraft.toml +0 -0
  91. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/treemap_output.txt +0 -0
  92. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/unbounded_output.txt +0 -0
  93. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/vectorization_output.txt +0 -0
  94. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/vectorize_output.txt +0 -0
  95. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/water_tank_output.txt +0 -0
  96. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples/x_greaterthan_1_output.txt +0 -0
  97. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/examples.rst +0 -0
  98. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/Mission.gif +0 -0
  99. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/performance_modeling.svg +0 -0
  100. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/referencesplot.png +0 -0
  101. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/Mission.png +0 -0
  102. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/Mission.svg +0 -0
  103. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/Model.png +0 -0
  104. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/Model.svg +0 -0
  105. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission.png +0 -0
  106. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission.svg +0 -0
  107. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_Aircraft.Wing.Planform.b.png +0 -0
  108. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_Aircraft.Wing.Planform.b.svg +0 -0
  109. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_CFRPFabric.tmin.png +0 -0
  110. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_CFRPFabric.tmin.svg +0 -0
  111. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_Nprop.png +0 -0
  112. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_Nprop.svg +0 -0
  113. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_Wtotal.png +0 -0
  114. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sankey_autosaves/SolarMission_Wtotal.svg +0 -0
  115. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/sizedconstrainttreemap.png +0 -0
  116. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/solartest.py +0 -0
  117. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/figures/treemap.png +0 -0
  118. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/gettingstarted.rst +0 -0
  119. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/gp101.rst +0 -0
  120. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/gplogo.png +0 -0
  121. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/gplogo.svg +0 -0
  122. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/index.rst +0 -0
  123. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/installation.rst +0 -0
  124. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/modelbuilding.rst +0 -0
  125. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/modeling_conventions.md +0 -0
  126. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/releasenotes.rst +0 -0
  127. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/signomialprogramming.rst +0 -0
  128. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/docs/source/visint.rst +0 -0
  129. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/ast_nodes.py +0 -0
  130. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/breakdowns.py +0 -0
  131. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/budgets.py +0 -0
  132. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/__init__.py +0 -0
  133. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/bounded.py +0 -0
  134. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/costed.py +0 -0
  135. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/loose.py +0 -0
  136. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/relax.py +0 -0
  137. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/set.py +0 -0
  138. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/sigeq.py +0 -0
  139. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/constraints/tight.py +0 -0
  140. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/__init__.py +0 -0
  141. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/autosweep.py +0 -0
  142. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/beam.py +0 -0
  143. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/bemt_hover.py +0 -0
  144. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/boundschecking.py +0 -0
  145. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/breakdowns.py +0 -0
  146. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/checking_result_changes.py +0 -0
  147. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/debug.py +0 -0
  148. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/evaluated_fixed_variables.py +0 -0
  149. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/evaluated_free_variables.py +0 -0
  150. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/external_constraint.py +0 -0
  151. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/external_function.py +0 -0
  152. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/external_sp.py +0 -0
  153. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/freeing_fixed_variables.py +0 -0
  154. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/fuel_burn.py +0 -0
  155. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/gettingstarted.py +0 -0
  156. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/gp_textbook.py +0 -0
  157. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/issue_1513.py +0 -0
  158. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/issue_1522.py +0 -0
  159. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/loose_constraintsets.py +0 -0
  160. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/migp.py +0 -0
  161. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/model_var_access.py +0 -0
  162. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/performance_modeling.py +0 -0
  163. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/plot_sweep1d.py +0 -0
  164. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/primal_infeasible_ex1.py +0 -0
  165. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/primal_infeasible_ex2.py +0 -0
  166. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/relaxation.py +0 -0
  167. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/simple_box.py +0 -0
  168. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/simple_sp.py +0 -0
  169. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/simpleflight.py +0 -0
  170. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/sin_approx_example.py +0 -0
  171. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/sp_to_gp_sweep.py +0 -0
  172. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/sub_multi_values.py +0 -0
  173. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/substitutions.py +0 -0
  174. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/tight_constraintsets.py +0 -0
  175. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/treemap.py +0 -0
  176. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/unbounded.py +0 -0
  177. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/vectorization.py +0 -0
  178. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/vectorize.py +0 -0
  179. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/water_tank.py +0 -0
  180. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/examples/x_greaterthan_1.py +0 -0
  181. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/exceptions.py +0 -0
  182. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/interactive/__init__.py +0 -0
  183. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/interactive/plot_sweep.py +0 -0
  184. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/interactive/plotting.py +0 -0
  185. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/interactive/references.py +0 -0
  186. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/interactive/referencesplot.html +0 -0
  187. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/interactive/sankey.py +0 -0
  188. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/interactive/widgets.py +0 -0
  189. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/__init__.py +0 -0
  190. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/array.py +0 -0
  191. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/constraints.py +0 -0
  192. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/data.py +0 -0
  193. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/map.py +0 -0
  194. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/math.py +0 -0
  195. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/substitution.py +0 -0
  196. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/nomials/variables.py +0 -0
  197. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/printing.py +0 -0
  198. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/programs/__init__.py +0 -0
  199. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/programs/gp.py +0 -0
  200. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/programs/prog_factories.py +0 -0
  201. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/programs/sgp.py +0 -0
  202. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/solutions.py +0 -0
  203. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/solvers/__init__.py +0 -0
  204. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/solvers/cvxopt.py +0 -0
  205. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/solvers/mosek_cli.py +0 -0
  206. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/solvers/mosek_conif.py +0 -0
  207. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/__init__.py +0 -0
  208. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/conftest.py +0 -0
  209. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_budgets.py +0 -0
  210. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_catalog.py +0 -0
  211. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_constraints.py +0 -0
  212. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_examples.py +0 -0
  213. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_ir.py +0 -0
  214. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_model.py +0 -0
  215. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_model_graph.py +0 -0
  216. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_nomial_array.py +0 -0
  217. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_nomials.py +0 -0
  218. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_solution.py +0 -0
  219. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_sub.py +0 -0
  220. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_toml_expr.py +0 -0
  221. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_toml_parser.py +0 -0
  222. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_toml_printer.py +0 -0
  223. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_toml_subs.py +0 -0
  224. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_tools.py +0 -0
  225. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_util.py +0 -0
  226. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_varmap.py +0 -0
  227. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_vars.py +0 -0
  228. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tests/test_varset.py +0 -0
  229. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/toml/__init__.py +0 -0
  230. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/toml/_expr.py +0 -0
  231. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/toml/_parser.py +0 -0
  232. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/toml/_printer.py +0 -0
  233. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/toml/_subs.py +0 -0
  234. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tools/__init__.py +0 -0
  235. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tools/autosweep.py +0 -0
  236. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/tools/tools.py +0 -0
  237. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/units.py +0 -0
  238. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/util/__init__.py +0 -0
  239. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/util/build.py +0 -0
  240. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/util/globals.py +0 -0
  241. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/util/repr_conventions.py +0 -0
  242. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/util/small_classes.py +0 -0
  243. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/util/small_scripts.py +0 -0
  244. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/var.py +0 -0
  245. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/varkey.py +0 -0
  246. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/gpkit/varmap.py +0 -0
  247. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/linecount.sh +0 -0
  248. {gpkit_core-0.3.2 → gpkit_core-0.3.4}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gpkit-core
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Package for defining and manipulating geometric programming models.
5
5
  Project-URL: Homepage, https://www.github.com/beautifulmachines/gpkit-core
6
6
  Author-email: Warren Hoburg <whoburg@alum.mit.edu>
@@ -1,6 +1,6 @@
1
1
  "GP and SP modeling package"
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.4"
4
4
 
5
5
  import numpy as _np
6
6
 
@@ -343,7 +343,30 @@ class Model(CostedConstraintSet): # pylint: disable=too-many-instance-attribute
343
343
  ir_doc = json.loads(Path(path).read_text(encoding="utf-8"))
344
344
  return cls.from_ir(ir_doc)
345
345
 
346
- def report(self, solution=None, fmt="text"):
346
+ @classmethod
347
+ def report_preamble(cls) -> str:
348
+ """Return optional markdown prose prepended before this model's report section.
349
+
350
+ Override in Model subclasses to attach section-specific context to a
351
+ specific part of a hierarchical report. The returned string is placed
352
+ before the section heading (and before the model's description/variables
353
+ tables).
354
+
355
+ Example::
356
+
357
+ class Wing(Model):
358
+ @classmethod
359
+ def report_preamble(cls):
360
+ return "This section covers aerodynamic and structural wing sizing."
361
+
362
+ For preambles that depend on model-specific counts (free variables,
363
+ constraints, objective), use :func:`gpkit.report.feasibility_block`
364
+ and :func:`gpkit.report.sensitivities_block` instead, and pass the
365
+ result via the *front_matter* argument of :meth:`report`.
366
+ """
367
+ return ""
368
+
369
+ def report(self, solution=None, fmt="text", front_matter="", toc=False):
347
370
  """Build a hierarchical report for this model.
348
371
 
349
372
  Parameters
@@ -352,11 +375,26 @@ class Model(CostedConstraintSet): # pylint: disable=too-many-instance-attribute
352
375
  If provided, variable tables include solved values and sensitivities.
353
376
  fmt : str
354
377
  Output format: "dict", "text", "md", or "latex".
378
+ front_matter : str, optional
379
+ Raw text/markdown prepended before the entire report (before the
380
+ root model's heading). Combine with
381
+ :func:`gpkit.report.feasibility_block` and
382
+ :func:`gpkit.report.sensitivities_block` to add standard GP
383
+ explanatory text::
384
+
385
+ from gpkit.report import feasibility_block, sensitivities_block
386
+ m.report(sol, fmt="md",
387
+ front_matter=feasibility_block(m) + "\\n\\n"
388
+ + sensitivities_block())
389
+ toc : bool, optional
390
+ If True, a table-of-contents marker is inserted (Markdown only).
355
391
  """
356
392
  # pylint: disable=import-outside-toplevel
357
393
  from .report import build_report_ir, render_report
358
394
 
359
- ir = build_report_ir(self, solution=solution)
395
+ ir = build_report_ir(
396
+ self, solution=solution, front_matter=front_matter, toc=toc
397
+ )
360
398
  return render_report(ir, fmt=fmt)
361
399
 
362
400
  gp = progify(GeometricProgram)
@@ -26,7 +26,12 @@ def nomial_latex_helper(c, pos_vars, neg_vars):
26
26
  cstr = f"{cstr[:idx]} \\times 10^{{int(cstr[idx + 1:])}}"
27
27
 
28
28
  if pos_vars and neg_vars:
29
- return f"{cstr}\\frac{{{pvarstr}}}{{{nvarstr}}}"
29
+ if cstr in (
30
+ "",
31
+ "-",
32
+ ): # coefficient is ±1; sign (or nothing) before the fraction
33
+ return f"{cstr}\\frac{{{pvarstr}}}{{{nvarstr}}}"
34
+ return f"\\frac{{{cstr} {pvarstr}}}{{{nvarstr}}}" # numeric coeff in numerator
30
35
  if neg_vars and not pos_vars:
31
36
  return f"\\frac{{{cstr}}}{{{nvarstr}}}"
32
37
  if pos_vars:
@@ -10,6 +10,7 @@ from typing import Any, List, Optional, Tuple
10
10
 
11
11
  from .constraints.tight import Tight
12
12
  from .model import Model as _Model
13
+ from .nomials import Variable
13
14
  from .printing import _format_aligned_columns
14
15
  from .util.repr_conventions import unitstr
15
16
  from .util.small_classes import Quantity
@@ -79,10 +80,12 @@ class ReportSection: # pylint: disable=too-many-instance-attributes
79
80
  )
80
81
  objective_str: str = "" # text representation of cost expression; "" if constant
81
82
  objective_latex: str = "" # LaTeX representation of cost expression
83
+ objective_label: str = "" # variable label when expr is a single variable; else ""
82
84
  objective_value: Optional[float] = (
83
85
  None # magnitude of attained cost; None if unsolved
84
86
  )
85
87
  objective_units: str = "" # unit string for the cost expression
88
+ objective_direction: str = "minimize" # "minimize" or "maximize"
86
89
 
87
90
  def to_dict(self) -> dict:
88
91
  """JSON-serializable dict (for format='dict' and future API)."""
@@ -90,6 +93,8 @@ class ReportSection: # pylint: disable=too-many-instance-attributes
90
93
  "title": self.title,
91
94
  "lineage_path": self.lineage_path,
92
95
  "magic_prefix": self.magic_prefix,
96
+ "objective_direction": self.objective_direction,
97
+ "objective_label": self.objective_label,
93
98
  "is_anonymous": self.is_anonymous,
94
99
  "description": self.description,
95
100
  "assumptions": list(self.assumptions),
@@ -324,27 +329,201 @@ def _build_constraint_groups(model) -> List[CGroup]:
324
329
  return [CGroup(label="", constraints=own)] if own else []
325
330
 
326
331
 
332
+ def _reciprocal_if_1_over_x(cost):
333
+ """Return (True, 1/cost) if cost is a single monomial 1/expr, else (False, cost).
334
+
335
+ Detects the pattern coeff=1, all-negative exponents — the 1/x form that
336
+ GPs use to express maximization. Mirrors the TOML printer's _is_reciprocal
337
+ check but operates on the live cost object rather than the IR AST dict.
338
+ """
339
+ hmap = cost.hmap
340
+ if len(hmap) != 1:
341
+ return False, cost
342
+ (exp,) = hmap.keys()
343
+ coeff = hmap[exp]
344
+ exp_dict = dict(exp)
345
+ if abs(coeff - 1.0) < 1e-10 and exp_dict and all(v < 0 for v in exp_dict.values()):
346
+ # Build the inner expression from VarKeys rather than computing 1/cost.
347
+ # 1/cost encodes div(1, cost.ast) in its AST, causing str/latex to
348
+ # render as 1/(1/x) instead of x.
349
+ inner = None
350
+ for vk, e in exp_dict.items():
351
+ term = Variable(vk) if e == -1 else Variable(vk) ** (-e)
352
+ inner = term if inner is None else inner * term
353
+ return True, inner
354
+ return False, cost
355
+
356
+
327
357
  def _build_objective(model, solution) -> dict:
328
358
  """Return objective keyword args for ReportSection for the model's cost.
329
359
 
330
360
  All fields are empty/None when the cost has no variables (i.e. it is a
331
361
  trivial constant placeholder rather than a real optimization objective).
362
+ Detects the 1/expr pattern and flips direction to "maximize".
332
363
  """
333
364
  if not model.cost.vks:
334
365
  return {
335
366
  "objective_str": "",
336
367
  "objective_latex": "",
368
+ "objective_label": "",
337
369
  "objective_value": None,
338
370
  "objective_units": "",
371
+ "objective_direction": "minimize",
339
372
  }
373
+ is_recip, expr = _reciprocal_if_1_over_x(model.cost)
374
+ cost_value = (
375
+ (1.0 / float(solution.cost) if is_recip else float(solution.cost))
376
+ if solution is not None
377
+ else None
378
+ )
379
+ excluded = {"units", "lineage"}
380
+ vks = list(expr.vks)
381
+ label = vks[0].label if len(vks) == 1 else ""
382
+ return {
383
+ "objective_str": expr.str_without(excluded),
384
+ "objective_latex": expr.latex(excluded),
385
+ "objective_label": label or "",
386
+ "objective_value": cost_value,
387
+ "objective_units": unitstr(expr),
388
+ "objective_direction": "maximize" if is_recip else "minimize",
389
+ }
390
+
391
+
392
+ # ── Standard text blocks ──────────────────────────────────────────────────────
393
+
394
+
395
+ def _model_stats(model) -> dict:
396
+ """Compute model-wide counts for use in report text blocks.
397
+
398
+ Returns a dict with keys: n_free, n_constraints, objective_str,
399
+ objective_latex. All values are derived from the model without requiring
400
+ a solved solution.
401
+ """
402
+ n_free = len(model.vks) - len(model.substitutions)
403
+ # flat() returns a generator (flatiter), so use sum() rather than len().
404
+ n_constraints = sum(1 for _ in model.flat())
405
+ if model.cost.vks:
406
+ is_recip, expr = _reciprocal_if_1_over_x(model.cost)
407
+ excluded = {"units", "lineage"}
408
+ vks = list(expr.vks)
409
+ obj_latex = expr.latex(excluded)
410
+ obj_label = vks[0].label if len(vks) == 1 else ""
411
+ obj_direction = "maximize" if is_recip else "minimize"
412
+ else:
413
+ obj_latex = None
414
+ obj_label = ""
415
+ obj_direction = "minimize"
340
416
  return {
341
- "objective_str": model.cost.str_without({"units"}),
342
- "objective_latex": model.cost.latex({"units"}),
343
- "objective_value": float(solution.cost) if solution is not None else None,
344
- "objective_units": unitstr(model.cost),
417
+ "n_free": n_free,
418
+ "n_constraints": n_constraints,
419
+ "objective_latex": obj_latex,
420
+ "objective_label": obj_label or "",
421
+ "objective_direction": obj_direction,
345
422
  }
346
423
 
347
424
 
425
+ def feasibility_block(model) -> str:
426
+ """Return a markdown explanation of feasibility and optimality for *model*.
427
+
428
+ Fills in the number of free variables, constraints, and current objective
429
+ expression. Suitable for use as ``front_matter`` or a ``report_preamble``
430
+ in a custom report.
431
+
432
+ Example usage::
433
+
434
+ from gpkit.report import feasibility_block, sensitivities_block
435
+
436
+ class Aircraft(Model):
437
+ ...
438
+
439
+ m = Aircraft()
440
+ sol = m.solve()
441
+ print(m.report(sol, fmt="md",
442
+ front_matter=feasibility_block(m) + "\\n\\n"
443
+ + sensitivities_block()))
444
+ """
445
+ ctx = _model_stats(model)
446
+ if ctx["objective_latex"]:
447
+ direction = ctx["objective_direction"]
448
+ label_clause = f", {ctx['objective_label']}" if ctx["objective_label"] else ""
449
+ obj_clause = (
450
+ f" The objective is currently set to {direction}"
451
+ f" ${ctx['objective_latex']}${label_clause}."
452
+ )
453
+ else:
454
+ obj_clause = ""
455
+ return (
456
+ f"## Feasibility and Optimality\n\n"
457
+ f"The model currently has {ctx['n_free']} free variables and "
458
+ f"{ctx['n_constraints']} constraints. A design satisfying all "
459
+ f"constraints is *feasible*; the set of all feasible designs is the "
460
+ f"*feasible set*."
461
+ f"{obj_clause} "
462
+ f"The solver finds a globally optimal solution — the unique feasible "
463
+ f"design that cannot be improved further — with a reliable, efficient "
464
+ f"algorithm. This guarantee comes from the convex structure of the "
465
+ f"problem, not from luck or tuning."
466
+ )
467
+
468
+
469
+ SENSITIVITIES_BLOCK = (
470
+ "## Sensitivities\n\n"
471
+ "Each fixed constant in the model has a *sensitivity* — a number that "
472
+ "tells you how much the objective would change if that parameter changed. "
473
+ "Specifically, if a constant $c$ has sensitivity $s$, then increasing $c$ "
474
+ "by 1% would worsen the objective by approximately $s$% (holding all other "
475
+ "constants fixed and re-solving). A sensitivity of 1.5 means a 1% increase "
476
+ "in that constant would worsen the objective by 1.5%; a sensitivity of −0.8 "
477
+ "means a 1% increase would improve the objective by 0.8%. "
478
+ "Sensitivities with large magnitude flag the parameters that matter most; "
479
+ "near-zero sensitivities indicate parameters the design is insensitive to. "
480
+ "These numbers come for free alongside every solve — no extra computation "
481
+ "required."
482
+ )
483
+
484
+
485
+ def sensitivities_block() -> str:
486
+ """Return a markdown explanation of GP dual solution / sensitivity information.
487
+
488
+ This text is model-independent and can be included in any GP report.
489
+ """
490
+ return SENSITIVITIES_BLOCK
491
+
492
+
493
+ def objective_block(model, solution=None) -> str:
494
+ """Return a markdown summary of the model's objective for use in a report.
495
+
496
+ Shows the objective direction (minimize/maximize), the expression without
497
+ lineage, the variable label when the expression is a single variable, and
498
+ the attained value when *solution* is provided.
499
+
500
+ Like :func:`feasibility_block` and :func:`sensitivities_block`, this
501
+ returns a plain markdown string so it can be placed wherever the author
502
+ wants — front matter, a subsection preamble, or anywhere else.
503
+
504
+ Example::
505
+
506
+ from gpkit.report import objective_block
507
+ m.report(sol, fmt="md", front_matter=objective_block(m, sol))
508
+ """
509
+ if not model.cost.vks:
510
+ return ""
511
+ is_recip, expr = _reciprocal_if_1_over_x(model.cost)
512
+ excluded = {"units", "lineage"}
513
+ direction = "maximize" if is_recip else "minimize"
514
+ latex = expr.latex(excluded)
515
+ vks = list(expr.vks)
516
+ label_clause = f", {vks[0].label}" if len(vks) == 1 and vks[0].label else ""
517
+ lines = [f"**Objective:** {direction} ${latex}${label_clause}"]
518
+ if solution is not None:
519
+ cost_value = 1.0 / float(solution.cost) if is_recip else float(solution.cost)
520
+ val_str = _fmt_value(cost_value)
521
+ attained = f"{val_str} {unitstr(expr)}".rstrip()
522
+ lines.append("")
523
+ lines.append(f"**Attained:** {attained}")
524
+ return "\n".join(lines)
525
+
526
+
348
527
  # ── Core builder ─────────────────────────────────────────────────────────────
349
528
 
350
529
 
@@ -364,8 +543,10 @@ def build_report_ir(
364
543
  solution : Solution, optional
365
544
  If provided, variable entries include solved values and sensitivities.
366
545
  front_matter : str, optional
367
- Raw text/markdown prepended before the report. Set only on the root
368
- ReportSection; not propagated to children.
546
+ Raw text/markdown prepended before the root section. For the root
547
+ model, caller-supplied *front_matter* is combined with the model's
548
+ ``report_preamble()`` (if any). For child models, only
549
+ ``report_preamble()`` is used.
369
550
  toc : bool, optional
370
551
  If True, a table-of-contents marker is inserted by renderers that have
371
552
  a native TOC facility (e.g. ``[TOC]`` in Markdown). Set only on the
@@ -386,8 +567,16 @@ def build_report_ir(
386
567
  )
387
568
  if is_anon:
388
569
  desc = {"summary": "", "assumptions": [], "references": []}
570
+ section_fm = front_matter
389
571
  else:
390
572
  desc = type(model).description()
573
+ preamble = type(model).report_preamble()
574
+ if preamble and front_matter:
575
+ section_fm = front_matter + "\n\n" + preamble
576
+ elif preamble:
577
+ section_fm = preamble
578
+ else:
579
+ section_fm = front_matter
391
580
  return ReportSection(
392
581
  title=own_name or "Model",
393
582
  description=desc["summary"],
@@ -400,7 +589,7 @@ def build_report_ir(
400
589
  fixed_variables=fixed_vars,
401
590
  constraint_groups=cgroups,
402
591
  lineage_map=lineage_map,
403
- front_matter=front_matter,
592
+ front_matter=section_fm,
404
593
  toc=toc,
405
594
  **_build_objective(model, solution),
406
595
  children=[
@@ -570,13 +759,6 @@ def _text_prose_lines(ir: "ReportSection", pad: str) -> list:
570
759
  for item in ir.references:
571
760
  lines.append(f"{pad} - {item}")
572
761
  lines.append("")
573
- if ir.objective_str:
574
- lines.append(f"{pad} Objective")
575
- lines.append(f"{pad} minimize: {ir.objective_str}")
576
- if ir.objective_value is not None:
577
- val_str = _fmt_value(ir.objective_value)
578
- lines.append(f"{pad} attained: {val_str} {ir.objective_units}".rstrip())
579
- lines.append("")
580
762
  return lines
581
763
 
582
764
 
@@ -597,7 +779,6 @@ def render_text(ir: "ReportSection", indent: int = 0) -> str:
597
779
  pad = _INDENT * indent
598
780
  lines: list = []
599
781
 
600
- # Front matter (root only)
601
782
  if ir.front_matter:
602
783
  lines.append(ir.front_matter)
603
784
  lines.append("")
@@ -612,6 +793,9 @@ def render_text(ir: "ReportSection", indent: int = 0) -> str:
612
793
 
613
794
  lines.extend(_text_prose_lines(ir, pad))
614
795
 
796
+ # Constraint groups
797
+ lines.extend(_text_cgroup_lines(ir.constraint_groups, pad, ir.lineage_map))
798
+
615
799
  # Optimized Variables table (primal — no sensitivity column)
616
800
  if ir.free_variables:
617
801
  lines.append(f"{pad} Optimized Variables")
@@ -626,9 +810,6 @@ def render_text(ir: "ReportSection", indent: int = 0) -> str:
626
810
  lines.append(f"{pad} {row_line}")
627
811
  lines.append("")
628
812
 
629
- # Constraint groups
630
- lines.extend(_text_cgroup_lines(ir.constraint_groups, pad, ir.lineage_map))
631
-
632
813
  # Children (recursive)
633
814
  for child in ir.children:
634
815
  lines.append(render_text(child, indent=child_indent))
@@ -696,13 +877,6 @@ def _md_prose_lines(ir: "ReportSection") -> list:
696
877
  if ir.references:
697
878
  lines.append(f"**References:** {'; '.join(ir.references)}")
698
879
  lines.append("")
699
- if ir.objective_str:
700
- lines.append(f"**Objective:** minimize $${ir.objective_latex}$$")
701
- if ir.objective_value is not None:
702
- val_str = _fmt_value(ir.objective_value)
703
- attained = f"{val_str} {ir.objective_units}".rstrip()
704
- lines.append(f"**Attained:** {attained}")
705
- lines.append("")
706
880
  return lines
707
881
 
708
882
 
@@ -723,7 +897,7 @@ def render_markdown(ir: "ReportSection", level: int = 1) -> str:
723
897
  hdr = "#" * min(level, 6)
724
898
  lines: list = []
725
899
 
726
- # Front matter and TOC marker (root only, before first heading)
900
+ # Front matter and TOC marker (before first heading)
727
901
  if ir.front_matter:
728
902
  lines.append(ir.front_matter)
729
903
  lines.append("")
@@ -742,20 +916,6 @@ def render_markdown(ir: "ReportSection", level: int = 1) -> str:
742
916
 
743
917
  lines.extend(_md_prose_lines(ir))
744
918
 
745
- # Optimized Variables pipe table (no sensitivity column)
746
- if ir.free_variables:
747
- lines.append("**Optimized Variables**")
748
- lines.append("")
749
- lines.extend(_md_var_table(ir.free_variables))
750
- lines.append("")
751
-
752
- # Fixed Variables pipe table (value | units | sensitivity | label)
753
- if ir.fixed_variables:
754
- lines.append("**Fixed Variables**")
755
- lines.append("")
756
- lines.extend(_md_var_table(ir.fixed_variables, include_sensitivity=True))
757
- lines.append("")
758
-
759
919
  # Constraint groups
760
920
  excluded = ("units", ":MAGIC:" + ir.magic_prefix) if ir.magic_prefix else ("units",)
761
921
  for cg in ir.constraint_groups:
@@ -773,6 +933,20 @@ def render_markdown(ir: "ReportSection", level: int = 1) -> str:
773
933
  lines.append("\\end{aligned}$$")
774
934
  lines.append("")
775
935
 
936
+ # Optimized Variables pipe table (no sensitivity column)
937
+ if ir.free_variables:
938
+ lines.append("**Optimized Variables**")
939
+ lines.append("")
940
+ lines.extend(_md_var_table(ir.free_variables))
941
+ lines.append("")
942
+
943
+ # Fixed Variables pipe table (value | units | sensitivity | label)
944
+ if ir.fixed_variables:
945
+ lines.append("**Fixed Variables**")
946
+ lines.append("")
947
+ lines.extend(_md_var_table(ir.fixed_variables, include_sensitivity=True))
948
+ lines.append("")
949
+
776
950
  # Children (recursive)
777
951
  for child in ir.children:
778
952
  child_md = render_markdown(child, level=child_level)
@@ -347,6 +347,36 @@ class TestRenderText:
347
347
  # Should contain variable names from constraints
348
348
  assert "x_ca" in result
349
349
 
350
+ def test_report_section_order_text(self):
351
+ """Constraints appear before Optimized/Fixed Variables in text output."""
352
+
353
+ class _OrderM(Model):
354
+ def setup(self):
355
+ x = Variable("x_ord_t")
356
+ x_max = Variable("x_ord_max", 10)
357
+ return [x >= 2, x <= x_max]
358
+
359
+ m = _OrderM()
360
+ sol = m.solve(verbosity=0)
361
+ result = m.report(solution=sol, fmt="text")
362
+ assert result.index("Constraints") < result.index("Optimized Variables")
363
+ assert result.index("Optimized Variables") < result.index("Fixed Variables")
364
+
365
+ def test_report_section_order_markdown(self):
366
+ """Constraints appear before Optimized/Fixed Variables in markdown output."""
367
+
368
+ class _OrderMd(Model):
369
+ def setup(self):
370
+ x = Variable("x_ord_md")
371
+ x_max = Variable("x_ord_md_max", 10)
372
+ return [x >= 2, x <= x_max]
373
+
374
+ m = _OrderMd()
375
+ sol = m.solve(verbosity=0)
376
+ result = m.report(solution=sol, fmt="md")
377
+ assert result.index("Constraints") < result.index("Optimized Variables")
378
+ assert result.index("Optimized Variables") < result.index("Fixed Variables")
379
+
350
380
  def test_report_text_with_solution(self):
351
381
  """model.report(solution=sol, fmt='text') includes variable values."""
352
382
 
@@ -12,6 +12,7 @@ from gpkit import Model, Variable
12
12
  from gpkit.report import (
13
13
  ReportSection,
14
14
  build_report_ir,
15
+ objective_block,
15
16
  render_markdown,
16
17
  render_text,
17
18
  )
@@ -388,11 +389,16 @@ class TestObjective:
388
389
  assert ir.objective_units != ""
389
390
 
390
391
  def test_build_ir_populates_objective_solved(self):
391
- """build_report_ir populates objective_value from solution."""
392
+ """build_report_ir populates objective_value from solution.
393
+
394
+ Box uses cost=1/(h*w*d), so the direction flips to "maximize" and
395
+ objective_value is the maximized volume (1/sol.cost), not sol.cost.
396
+ """
392
397
  m, sol = self._solved_box()
393
398
  ir = build_report_ir(m, solution=sol)
394
399
  assert ir.objective_value is not None
395
- assert ir.objective_value == pytest.approx(sol.cost)
400
+ assert ir.objective_direction == "maximize"
401
+ assert ir.objective_value == pytest.approx(1.0 / sol.cost)
396
402
 
397
403
  def test_build_ir_objective_empty_for_constant_cost(self):
398
404
  """build_report_ir leaves objective fields empty when cost has no variables."""
@@ -431,18 +437,22 @@ class TestObjective:
431
437
  assert ir.children[0].objective_str == "" # child does not
432
438
 
433
439
  def test_render_text_objective(self):
434
- """render_text includes objective expression and value when present."""
440
+ """Objective is no longer auto-rendered; use objective_block() instead."""
435
441
  m, sol = self._solved_box()
436
442
  out = m.report(solution=sol, fmt="text")
437
- assert "Objective" in out
438
- assert "minimize" in out.lower()
443
+ assert "Objective" not in out # removed from auto-render
444
+ block = objective_block(m, sol)
445
+ assert "maximize" in block.lower()
446
+ assert "Attained" in block
439
447
 
440
448
  def test_render_markdown_objective(self):
441
- """render_markdown includes objective heading and value when present."""
449
+ """Objective is no longer auto-rendered; use objective_block() instead."""
442
450
  m, sol = self._solved_box()
443
451
  out = m.report(solution=sol, fmt="md")
444
- assert "Objective" in out
445
- assert "minimize" in out.lower()
452
+ assert "Objective" not in out # removed from auto-render
453
+ block = objective_block(m, sol)
454
+ assert "maximize" in block.lower()
455
+ assert "Attained" in block
446
456
 
447
457
  def test_render_text_no_objective_when_empty(self):
448
458
  """render_text omits objective section when cost has no variables."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes