pyfemtet 0.9.6__py3-none-any.whl → 1.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of pyfemtet might be problematic. Click here for more details.

Files changed (272) hide show
  1. pyfemtet/__init__.py +6 -1
  2. pyfemtet/_i18n/1. make_pot_and_update_po.bat +8 -0
  3. pyfemtet/_i18n/2. build_mo.bat +5 -0
  4. pyfemtet/_i18n/__init__.py +4 -0
  5. pyfemtet/_i18n/babel.cfg +2 -0
  6. pyfemtet/_i18n/i18n.py +37 -0
  7. pyfemtet/_i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  8. pyfemtet/_i18n/locales/ja/LC_MESSAGES/messages.po +1020 -0
  9. pyfemtet/_i18n/locales/messages.pot +987 -0
  10. pyfemtet/{_message → _i18n}/messages.py +128 -41
  11. pyfemtet/_util/closing.py +19 -0
  12. pyfemtet/_util/dask_util.py +89 -7
  13. pyfemtet/_util/df_util.py +46 -0
  14. pyfemtet/_util/excel_macro_util.py +8 -3
  15. pyfemtet/_util/excel_parse_util.py +43 -23
  16. pyfemtet/_util/femtet_access_inspection.py +120 -0
  17. pyfemtet/{_femtet_config_util/autosave.py → _util/femtet_autosave.py} +7 -0
  18. pyfemtet/_util/femtet_exit.py +105 -0
  19. pyfemtet/_util/femtet_version.py +20 -0
  20. pyfemtet/_util/helper.py +103 -0
  21. pyfemtet/_util/process_util.py +107 -0
  22. pyfemtet/_util/str_enum.py +44 -0
  23. pyfemtet/core.py +15 -47
  24. pyfemtet/dispatch_extensions/__init__.py +8 -11
  25. pyfemtet/dispatch_extensions/_impl.py +42 -198
  26. pyfemtet/logger/__init__.py +8 -1
  27. pyfemtet/logger/_impl.py +5 -6
  28. pyfemtet/opt/__init__.py +3 -17
  29. pyfemtet/opt/exceptions.py +45 -0
  30. pyfemtet/opt/femopt.py +621 -0
  31. pyfemtet/opt/history/__init__.py +11 -0
  32. pyfemtet/opt/history/_history.py +1416 -0
  33. pyfemtet/opt/history/_hypervolume.py +169 -0
  34. pyfemtet/opt/history/_optimality.py +79 -0
  35. pyfemtet/opt/interface/__init__.py +17 -24
  36. pyfemtet/opt/interface/_base_interface.py +222 -0
  37. pyfemtet/opt/interface/_excel_interface/__init__.py +3 -0
  38. pyfemtet/opt/interface/_excel_interface/debug-excel-interface.xlsm +0 -0
  39. pyfemtet/opt/interface/_excel_interface/excel_interface.py +997 -0
  40. pyfemtet/opt/interface/_femtet_interface/__init__.py +3 -0
  41. pyfemtet/opt/interface/{_femtet_parametric.py → _femtet_interface/_femtet_parametric.py} +20 -12
  42. pyfemtet/opt/interface/{_femtet.py → _femtet_interface/femtet_interface.py} +508 -353
  43. pyfemtet/opt/interface/_femtet_with_nx_interface/__init__.py +5 -0
  44. pyfemtet/opt/interface/_femtet_with_nx_interface/femtet_with_nx_interface.py +230 -0
  45. pyfemtet/opt/interface/_femtet_with_nx_interface/model1.prt +0 -0
  46. pyfemtet/opt/interface/_femtet_with_nx_interface/model1.x_t +98 -0
  47. pyfemtet/opt/interface/{_femtet_with_nx → _femtet_with_nx_interface}/update_model.py +1 -3
  48. pyfemtet/opt/interface/_femtet_with_solidworks/__init__.py +5 -0
  49. pyfemtet/opt/interface/_femtet_with_solidworks/femtet_with_solidworks_interface.py +142 -0
  50. pyfemtet/opt/interface/_solidworks_interface/__init__.py +5 -0
  51. pyfemtet/opt/interface/_solidworks_interface/solidworks_interface.py +227 -0
  52. pyfemtet/opt/interface/_surrogate_model_interface/__init__.py +8 -0
  53. pyfemtet/opt/interface/_surrogate_model_interface/base_surrogate_interface.py +150 -0
  54. pyfemtet/opt/interface/_surrogate_model_interface/botorch_interface.py +298 -0
  55. pyfemtet/opt/interface/_surrogate_model_interface/debug-pof-botorch.reccsv +18 -0
  56. pyfemtet/opt/interface/_with_excel_settings/__init__.py +61 -0
  57. pyfemtet/opt/interface/_with_excel_settings/with_excel_settings.py +134 -0
  58. pyfemtet/opt/meta_script/YAML_Generator.xlsm +0 -0
  59. pyfemtet/opt/meta_script/__main__.py +58 -36
  60. pyfemtet/opt/optimizer/__init__.py +7 -9
  61. pyfemtet/opt/optimizer/_base_optimizer.py +911 -0
  62. pyfemtet/opt/optimizer/optuna_optimizer/__init__.py +9 -0
  63. pyfemtet/opt/optimizer/optuna_optimizer/_optuna_attribute.py +63 -0
  64. pyfemtet/opt/optimizer/optuna_optimizer/_optuna_optimizer.py +796 -0
  65. pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/__init__.py +7 -0
  66. pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/debug-pof-botorch.reccsv +18 -0
  67. pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/enable_nonlinear_constraint.py +244 -0
  68. pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/pof_botorch_sampler.py +1249 -0
  69. pyfemtet/opt/optimizer/optuna_optimizer/wat_ex14_parametric_jp.femprj +0 -0
  70. pyfemtet/opt/optimizer/scipy_optimizer/__init__.py +1 -0
  71. pyfemtet/opt/optimizer/scipy_optimizer/_scipy_optimizer.py +383 -0
  72. pyfemtet/opt/prediction/__init__.py +7 -0
  73. pyfemtet/opt/prediction/_botorch_utils.py +133 -0
  74. pyfemtet/opt/prediction/_gpytorch_modules_extension.py +142 -0
  75. pyfemtet/opt/prediction/_helper.py +155 -0
  76. pyfemtet/opt/prediction/_model.py +118 -0
  77. pyfemtet/opt/problem/problem.py +304 -0
  78. pyfemtet/opt/problem/variable_manager/__init__.py +20 -0
  79. pyfemtet/opt/problem/variable_manager/_string_as_expression.py +115 -0
  80. pyfemtet/opt/problem/variable_manager/_variable_manager.py +295 -0
  81. pyfemtet/opt/visualization/history_viewer/__main__.py +5 -0
  82. pyfemtet/opt/visualization/{_base.py → history_viewer/_base_application.py} +18 -13
  83. pyfemtet/opt/visualization/history_viewer/_common_pages.py +150 -0
  84. pyfemtet/opt/visualization/{_complex_components → history_viewer/_complex_components}/alert_region.py +10 -5
  85. pyfemtet/opt/visualization/{_complex_components → history_viewer/_complex_components}/control_femtet.py +16 -13
  86. pyfemtet/opt/visualization/{_complex_components → history_viewer/_complex_components}/main_graph.py +117 -47
  87. pyfemtet/opt/visualization/{_complex_components → history_viewer/_complex_components}/pm_graph.py +159 -138
  88. pyfemtet/opt/visualization/history_viewer/_process_monitor/_application.py +173 -0
  89. pyfemtet/opt/visualization/history_viewer/_process_monitor/_pages.py +291 -0
  90. pyfemtet/opt/visualization/{_wrapped_components → history_viewer/_wrapped_components}/dbc.py +1 -1
  91. pyfemtet/opt/visualization/{_wrapped_components → history_viewer/_wrapped_components}/dcc.py +1 -1
  92. pyfemtet/opt/visualization/{_wrapped_components → history_viewer/_wrapped_components}/html.py +1 -1
  93. pyfemtet/opt/visualization/history_viewer/result_viewer/__main__.py +5 -0
  94. pyfemtet/opt/visualization/{result_viewer/application.py → history_viewer/result_viewer/_application.py} +6 -6
  95. pyfemtet/opt/visualization/{result_viewer/pages.py → history_viewer/result_viewer/_pages.py} +106 -82
  96. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08.csv +18 -0
  97. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08.db +0 -0
  98. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8.jpg +0 -0
  99. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8.log +45 -0
  100. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8.pdt +0 -0
  101. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_1.jpg +0 -0
  102. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_1.pdt +0 -0
  103. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_10.jpg +0 -0
  104. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_10.pdt +0 -0
  105. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_11.jpg +0 -0
  106. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_11.pdt +0 -0
  107. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_12.jpg +0 -0
  108. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_12.pdt +0 -0
  109. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_13.jpg +0 -0
  110. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_13.pdt +0 -0
  111. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_14.jpg +0 -0
  112. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_14.pdt +0 -0
  113. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_15.jpg +0 -0
  114. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_15.pdt +0 -0
  115. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_16.jpg +0 -0
  116. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_16.pdt +0 -0
  117. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_17.jpg +0 -0
  118. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_17.pdt +0 -0
  119. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_18.jpg +0 -0
  120. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_18.pdt +0 -0
  121. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_19.jpg +0 -0
  122. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_19.pdt +0 -0
  123. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_2.jpg +0 -0
  124. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_2.pdt +0 -0
  125. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_20.jpg +0 -0
  126. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_20.pdt +0 -0
  127. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_3.jpg +0 -0
  128. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_3.pdt +0 -0
  129. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_4.bgr +0 -0
  130. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_4.bnd +0 -0
  131. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_4.btr +0 -0
  132. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_4.jpg +0 -0
  133. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_4.mtl +0 -0
  134. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_4.pdt +0 -0
  135. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_4.prm +0 -0
  136. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_5.jpg +0 -0
  137. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_5.pdt +0 -0
  138. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_6.jpg +0 -0
  139. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_6.pdt +0 -0
  140. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_7.jpg +0 -0
  141. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_7.pdt +0 -0
  142. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_8.jpg +0 -0
  143. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_8.pdt +0 -0
  144. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_9.jpg +0 -0
  145. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.Results/ex8_trial_9.pdt +0 -0
  146. pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.femprj +0 -0
  147. pyfemtet/opt/visualization/plotter/main_figure_creator.py +536 -0
  148. pyfemtet/opt/visualization/plotter/pm_graph_creator.py +359 -0
  149. pyfemtet/opt/worker_status.py +120 -0
  150. {pyfemtet-0.9.6.dist-info → pyfemtet-1.0.0.dist-info}/METADATA +23 -24
  151. pyfemtet-1.0.0.dist-info/RECORD +172 -0
  152. pyfemtet-1.0.0.dist-info/entry_points.txt +3 -0
  153. pyfemtet/_femtet_config_util/exit.py +0 -59
  154. pyfemtet/_message/1. make_pot.bat +0 -11
  155. pyfemtet/_message/2. make_mo.bat +0 -6
  156. pyfemtet/_message/__init__.py +0 -5
  157. pyfemtet/_message/babel.cfg +0 -2
  158. pyfemtet/_message/locales/ja/LC_MESSAGES/messages.mo +0 -0
  159. pyfemtet/_message/locales/ja/LC_MESSAGES/messages.po +0 -570
  160. pyfemtet/_message/locales/messages.pot +0 -551
  161. pyfemtet/_warning.py +0 -87
  162. pyfemtet/brep/_impl.py +0 -18
  163. pyfemtet/opt/_femopt.py +0 -1007
  164. pyfemtet/opt/_femopt_core.py +0 -1169
  165. pyfemtet/opt/_test_utils/control_femtet.py +0 -39
  166. pyfemtet/opt/_test_utils/hyper_sphere.py +0 -24
  167. pyfemtet/opt/_test_utils/record_history.py +0 -130
  168. pyfemtet/opt/advanced_samples/excel_ui/(ref) original_project.femprj +0 -0
  169. pyfemtet/opt/advanced_samples/excel_ui/femtet-macro.xlsm +0 -0
  170. pyfemtet/opt/advanced_samples/excel_ui/pyfemtet-core.py +0 -291
  171. pyfemtet/opt/advanced_samples/excel_ui/test-pyfemtet-core.cmd +0 -22
  172. pyfemtet/opt/advanced_samples/restart/gal_ex13_parametric.femprj +0 -0
  173. pyfemtet/opt/advanced_samples/restart/gal_ex13_parametric_restart.py +0 -99
  174. pyfemtet/opt/advanced_samples/restart/gal_ex13_parametric_restart_jp.py +0 -102
  175. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data.py +0 -60
  176. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data_jp.py +0 -57
  177. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate.py +0 -100
  178. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate_jp.py +0 -90
  179. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_parametric.femprj +0 -0
  180. pyfemtet/opt/interface/_base.py +0 -101
  181. pyfemtet/opt/interface/_excel_interface.py +0 -984
  182. pyfemtet/opt/interface/_femtet_excel.py +0 -141
  183. pyfemtet/opt/interface/_femtet_with_nx/__init__.py +0 -3
  184. pyfemtet/opt/interface/_femtet_with_nx/_interface.py +0 -178
  185. pyfemtet/opt/interface/_femtet_with_sldworks.py +0 -298
  186. pyfemtet/opt/interface/_surrogate/__init__.py +0 -5
  187. pyfemtet/opt/interface/_surrogate/_base.py +0 -129
  188. pyfemtet/opt/interface/_surrogate/_chaospy.py +0 -71
  189. pyfemtet/opt/interface/_surrogate/_singletaskgp.py +0 -71
  190. pyfemtet/opt/interface/_surrogate_excel.py +0 -102
  191. pyfemtet/opt/optimizer/_base.py +0 -376
  192. pyfemtet/opt/optimizer/_optuna/_botorch_patch/enable_nonlinear_constraint.py +0 -220
  193. pyfemtet/opt/optimizer/_optuna/_optuna.py +0 -434
  194. pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +0 -1914
  195. pyfemtet/opt/optimizer/_scipy.py +0 -159
  196. pyfemtet/opt/optimizer/_scipy_scalar.py +0 -127
  197. pyfemtet/opt/optimizer/parameter.py +0 -113
  198. pyfemtet/opt/prediction/_base.py +0 -61
  199. pyfemtet/opt/prediction/single_task_gp.py +0 -119
  200. pyfemtet/opt/samples/femprj_sample/ParametricIF.femprj +0 -0
  201. pyfemtet/opt/samples/femprj_sample/ParametricIF.py +0 -29
  202. pyfemtet/opt/samples/femprj_sample/ParametricIF_test_result.reccsv +0 -13
  203. pyfemtet/opt/samples/femprj_sample/cad_ex01_NX.femprj +0 -0
  204. pyfemtet/opt/samples/femprj_sample/cad_ex01_NX.prt +0 -0
  205. pyfemtet/opt/samples/femprj_sample/cad_ex01_NX.py +0 -135
  206. pyfemtet/opt/samples/femprj_sample/cad_ex01_NX_test_result.reccsv +0 -23
  207. pyfemtet/opt/samples/femprj_sample/cad_ex01_SW.SLDPRT +0 -0
  208. pyfemtet/opt/samples/femprj_sample/cad_ex01_SW.femprj +0 -0
  209. pyfemtet/opt/samples/femprj_sample/cad_ex01_SW.py +0 -131
  210. pyfemtet/opt/samples/femprj_sample/cad_ex01_SW_test_result.reccsv +0 -23
  211. pyfemtet/opt/samples/femprj_sample/constrained_pipe.femprj +0 -0
  212. pyfemtet/opt/samples/femprj_sample/constrained_pipe.py +0 -96
  213. pyfemtet/opt/samples/femprj_sample/constrained_pipe_test_result.reccsv +0 -13
  214. pyfemtet/opt/samples/femprj_sample/gal_ex58_parametric.femprj +0 -0
  215. pyfemtet/opt/samples/femprj_sample/gal_ex58_parametric.py +0 -74
  216. pyfemtet/opt/samples/femprj_sample/gal_ex58_parametric_test_result.reccsv +0 -13
  217. pyfemtet/opt/samples/femprj_sample/gau_ex08_parametric.femprj +0 -0
  218. pyfemtet/opt/samples/femprj_sample/gau_ex08_parametric.py +0 -58
  219. pyfemtet/opt/samples/femprj_sample/gau_ex08_parametric_test_result.reccsv +0 -23
  220. pyfemtet/opt/samples/femprj_sample/gau_ex12_parametric.femprj +0 -0
  221. pyfemtet/opt/samples/femprj_sample/gau_ex12_parametric.py +0 -52
  222. pyfemtet/opt/samples/femprj_sample/her_ex40_parametric.femprj +0 -0
  223. pyfemtet/opt/samples/femprj_sample/her_ex40_parametric.py +0 -138
  224. pyfemtet/opt/samples/femprj_sample/her_ex40_parametric_test_result.reccsv +0 -18
  225. pyfemtet/opt/samples/femprj_sample/paswat_ex1_parametric.femprj +0 -0
  226. pyfemtet/opt/samples/femprj_sample/paswat_ex1_parametric.py +0 -60
  227. pyfemtet/opt/samples/femprj_sample/paswat_ex1_parametric_parallel.py +0 -61
  228. pyfemtet/opt/samples/femprj_sample/paswat_ex1_parametric_test_result.reccsv +0 -18
  229. pyfemtet/opt/samples/femprj_sample/wat_ex14_parametric.femprj +0 -0
  230. pyfemtet/opt/samples/femprj_sample/wat_ex14_parametric.py +0 -58
  231. pyfemtet/opt/samples/femprj_sample/wat_ex14_parametric_parallel.py +0 -58
  232. pyfemtet/opt/samples/femprj_sample/wat_ex14_parametric_test_result.reccsv +0 -18
  233. pyfemtet/opt/samples/femprj_sample_jp/ParametricIF_jp.femprj +0 -0
  234. pyfemtet/opt/samples/femprj_sample_jp/ParametricIF_jp.py +0 -29
  235. pyfemtet/opt/samples/femprj_sample_jp/cad_ex01_NX_jp.femprj +0 -0
  236. pyfemtet/opt/samples/femprj_sample_jp/cad_ex01_NX_jp.py +0 -129
  237. pyfemtet/opt/samples/femprj_sample_jp/cad_ex01_SW_jp.femprj +0 -0
  238. pyfemtet/opt/samples/femprj_sample_jp/cad_ex01_SW_jp.py +0 -125
  239. pyfemtet/opt/samples/femprj_sample_jp/constrained_pipe_jp.py +0 -93
  240. pyfemtet/opt/samples/femprj_sample_jp/gal_ex58_parametric_jp.femprj +0 -0
  241. pyfemtet/opt/samples/femprj_sample_jp/gal_ex58_parametric_jp.py +0 -70
  242. pyfemtet/opt/samples/femprj_sample_jp/gau_ex08_parametric_jp.femprj +0 -0
  243. pyfemtet/opt/samples/femprj_sample_jp/gau_ex08_parametric_jp.py +0 -57
  244. pyfemtet/opt/samples/femprj_sample_jp/gau_ex12_parametric_jp.py +0 -52
  245. pyfemtet/opt/samples/femprj_sample_jp/her_ex40_parametric_jp.femprj +0 -0
  246. pyfemtet/opt/samples/femprj_sample_jp/her_ex40_parametric_jp.py +0 -138
  247. pyfemtet/opt/samples/femprj_sample_jp/paswat_ex1_parametric_jp.femprj +0 -0
  248. pyfemtet/opt/samples/femprj_sample_jp/paswat_ex1_parametric_jp.py +0 -58
  249. pyfemtet/opt/samples/femprj_sample_jp/paswat_ex1_parametric_parallel_jp.py +0 -59
  250. pyfemtet/opt/samples/femprj_sample_jp/wat_ex14_parametric_jp.py +0 -56
  251. pyfemtet/opt/samples/femprj_sample_jp/wat_ex14_parametric_parallel_jp.py +0 -56
  252. pyfemtet/opt/visualization/_complex_components/main_figure_creator.py +0 -332
  253. pyfemtet/opt/visualization/_complex_components/pm_graph_creator.py +0 -201
  254. pyfemtet/opt/visualization/_process_monitor/application.py +0 -226
  255. pyfemtet/opt/visualization/_process_monitor/pages.py +0 -406
  256. pyfemtet/opt/visualization/_wrapped_components/__init__.py +0 -0
  257. pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
  258. pyfemtet-0.9.6.dist-info/RECORD +0 -158
  259. pyfemtet-0.9.6.dist-info/entry_points.txt +0 -3
  260. /pyfemtet/{_femtet_config_util → opt/problem}/__init__.py +0 -0
  261. /pyfemtet/{brep → opt/visualization/history_viewer}/__init__.py +0 -0
  262. /pyfemtet/opt/{_test_utils → visualization/history_viewer/_complex_components}/__init__.py +0 -0
  263. /pyfemtet/opt/{optimizer/_optuna → visualization/history_viewer/_process_monitor}/__init__.py +0 -0
  264. /pyfemtet/opt/{optimizer/_optuna/_botorch_patch → visualization/history_viewer/_wrapped_components}/__init__.py +0 -0
  265. /pyfemtet/opt/visualization/{_wrapped_components → history_viewer/_wrapped_components}/str_enum.py +0 -0
  266. /pyfemtet/opt/visualization/{result_viewer → history_viewer/result_viewer}/.gitignore +0 -0
  267. /pyfemtet/opt/visualization/{_complex_components → history_viewer/result_viewer}/__init__.py +0 -0
  268. /pyfemtet/opt/visualization/{_process_monitor → plotter}/__init__.py +0 -0
  269. /pyfemtet/opt/{samples/femprj_sample_jp/wat_ex14_parametric_jp.femprj → wat_ex14_parametric_jp.femprj} +0 -0
  270. {pyfemtet-0.9.6.dist-info → pyfemtet-1.0.0.dist-info}/LICENSE +0 -0
  271. {pyfemtet-0.9.6.dist-info → pyfemtet-1.0.0.dist-info}/LICENSE_THIRD_PARTY.txt +0 -0
  272. {pyfemtet-0.9.6.dist-info → pyfemtet-1.0.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,1416 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, TypeAlias, Literal
4
+
5
+ import os
6
+ import csv
7
+ import ast
8
+ import math
9
+ import json
10
+ import datetime
11
+ import dataclasses
12
+ from time import sleep
13
+ from contextlib import nullcontext
14
+
15
+ import numpy as np
16
+ import pandas as pd
17
+
18
+ import pyfemtet
19
+
20
+ from pyfemtet._i18n import *
21
+ from pyfemtet._util.df_util import *
22
+ from pyfemtet._util.dask_util import *
23
+ from pyfemtet._util.str_enum import StrEnum
24
+ from pyfemtet.opt.exceptions import *
25
+ from pyfemtet.opt.problem.problem import *
26
+ from pyfemtet.opt.problem.variable_manager import *
27
+ from pyfemtet.logger import get_module_logger
28
+
29
+ from pyfemtet.opt.history._optimality import *
30
+ from pyfemtet.opt.history._hypervolume import *
31
+
32
+ if TYPE_CHECKING:
33
+ from pyfemtet.opt.interface import AbstractFEMInterface
34
+
35
+
36
+ __all__ = [
37
+ 'TrialState',
38
+ 'History',
39
+ 'ColumnOrderMode',
40
+ 'Record',
41
+ 'create_err_msg_from_exception',
42
+ 'CorrespondingColumnNameRuler',
43
+ 'MAIN_FILTER',
44
+ ]
45
+
46
+ MAIN_FILTER: dict = {
47
+ 'sub_fidelity_name': MAIN_FIDELITY_NAME,
48
+ 'sub_sampling': float('nan')
49
+ }
50
+
51
+
52
+ logger = get_module_logger('opt.history', False)
53
+
54
+
55
+ def create_err_msg_from_exception(e: Exception):
56
+ """:meta private:"""
57
+ additional = ' '.join(map(str, e.args))
58
+ if additional == '':
59
+ return type(e).__name__
60
+ else:
61
+ return type(e).__name__ + f'({additional})'
62
+
63
+
64
+ class TrialState(StrEnum):
65
+
66
+ succeeded = 'Success'
67
+ skipped = 'Skip'
68
+ hard_constraint_violation = 'Hard constraint violation'
69
+ soft_constraint_violation = 'Soft constraint violation'
70
+
71
+ # Hidden Constraint
72
+ model_error = 'Model error'
73
+ mesh_error = 'Mesh error'
74
+ solve_error = 'Solve error'
75
+ post_error = 'Post-processing error'
76
+
77
+ unknown_error = 'Unknown error'
78
+ undefined = 'undefined'
79
+
80
+ @staticmethod
81
+ def get_corresponding_state_from_exception(e: Exception) -> TrialState:
82
+ """:meta private:"""
83
+ if isinstance(e, ModelError):
84
+ state = TrialState.model_error
85
+ elif isinstance(e, MeshError):
86
+ state = TrialState.mesh_error
87
+ elif isinstance(e, SolveError):
88
+ state = TrialState.solve_error
89
+ elif isinstance(e, PostProcessError):
90
+ state = TrialState.post_error
91
+ elif isinstance(e, HardConstraintViolation):
92
+ state = TrialState.hard_constraint_violation
93
+ elif isinstance(e, SkipSolve):
94
+ state = TrialState.skipped
95
+ else:
96
+ state = TrialState.unknown_error
97
+ return state
98
+
99
+ @staticmethod
100
+ def get_corresponding_exception_from_state(state: TrialState) -> Exception | None:
101
+ """:meta private:"""
102
+ if state == TrialState.model_error:
103
+ e = ModelError()
104
+ elif state == TrialState.mesh_error:
105
+ e = MeshError()
106
+ elif state == TrialState.solve_error:
107
+ e = SolveError()
108
+ elif state == TrialState.post_error:
109
+ e = PostProcessError()
110
+ elif state == TrialState.unknown_error:
111
+ e = Exception()
112
+ elif state == TrialState.hard_constraint_violation:
113
+ e = HardConstraintViolation
114
+ elif state == TrialState.skipped:
115
+ e = SkipSolve
116
+ else:
117
+ e = None
118
+ return e
119
+
120
+ @classmethod
121
+ def get_hidden_constraint_violation_states(cls):
122
+ """:meta private:"""
123
+ return [cls.get_corresponding_state_from_exception(exception_type())
124
+ for exception_type in _HiddenConstraintViolation.__pyfemtet_subclasses__]
125
+
126
+
127
+ class DataFrameWrapper:
128
+ """:meta private:"""
129
+
130
+ __df: pd.DataFrame
131
+ _lock_name = 'edit-df'
132
+ _dataset_name = 'df'
133
+
134
+ def __init__(self, df: pd.DataFrame):
135
+ self.set_df(df)
136
+
137
+ def __len__(self):
138
+ return len(self.get_df())
139
+
140
+ def __str__(self):
141
+ return self.get_df().__str__()
142
+
143
+ @property
144
+ def lock(self):
145
+ return Lock(self._lock_name)
146
+
147
+ @property
148
+ def lock_if_not_locked(self):
149
+ if self.lock.locked():
150
+ return nullcontext()
151
+ else:
152
+ return self.lock
153
+
154
+ def get_df(self, equality_filters: dict = None) -> pd.DataFrame:
155
+ """
156
+
157
+ Args:
158
+ equality_filters (dict, optional):
159
+ {column: value} formatted dict.
160
+ Each condition is considered as
161
+ an 'and' condition.
162
+
163
+ Defaults to no filter.
164
+
165
+ Returns (pd.DataFrame):
166
+
167
+ """
168
+
169
+ client = get_client()
170
+
171
+ # dask クラスターがある場合
172
+ if client is not None:
173
+
174
+ # あるけど with を抜けている場合
175
+ if client.scheduler is None:
176
+ df = self.__df
177
+
178
+ # 健在の場合
179
+ else:
180
+
181
+ df = None
182
+
183
+ with Lock('access_dataset_df'):
184
+ # datasets 内に存在する場合
185
+ if self._dataset_name in client.list_datasets():
186
+ df = client.get_dataset(self._dataset_name)
187
+
188
+ # set の前に get されることはあってはならない
189
+ else:
190
+ raise RuntimeError
191
+
192
+ assert df is not None
193
+
194
+ # dask クラスターがない場合
195
+ else:
196
+ df = self.__df
197
+
198
+ # filter に合致するものを取得
199
+ if equality_filters is not None:
200
+ df = get_partial_df(df, equality_filters)
201
+
202
+ return df
203
+
204
+ def set_df(self, df, equality_filters: dict = None):
205
+ """
206
+
207
+ Args:
208
+ df:
209
+ equality_filters (dict, optional):
210
+ {column: value} formatted dict.
211
+ Each condition is considered as
212
+ an 'and' condition.
213
+ Only the indexed rows will be updated.
214
+
215
+ Defaults to no filter.
216
+
217
+ Returns (pd.DataFrame):
218
+
219
+ """
220
+
221
+ # フィルタを適用
222
+ # partial_df を get_df した時点のものから
223
+ # 変わっていたらエラーになる
224
+ if equality_filters is not None:
225
+ assert self.lock.locked(), 'set_df() with equality_filters must be called with locking.'
226
+ partial_df = df
227
+ df = self.get_df()
228
+ apply_partial_df(df, partial_df, equality_filters)
229
+
230
+ # dask クラスター上のデータを更新
231
+ client = get_client()
232
+ if client is not None:
233
+ if client.scheduler is not None:
234
+ with Lock('access_dataset_df'):
235
+
236
+ # datasets 上に存在する場合は削除(上書きができない)
237
+ if self._dataset_name in client.list_datasets():
238
+
239
+ # remove
240
+ client.unpublish_dataset(self._dataset_name)
241
+
242
+ # update
243
+ client.publish_dataset(**{self._dataset_name: df})
244
+ sleep(0.1)
245
+
246
+ # local のデータを更新
247
+ self.__df = df
248
+
249
+ def start_dask(self):
250
+ # Register the df initialized before dask context.
251
+ self.set_df(self.__df)
252
+
253
+ def end_dask(self):
254
+ # Get back the df on dask to use the value outside
255
+ # dask context.
256
+ self.__df = self.get_df()
257
+
258
+
259
+ class CorrespondingColumnNameRuler:
260
+ """:meta private:"""
261
+
262
+ @staticmethod
263
+ def direction_name(obj_name):
264
+ return obj_name + '_direction'
265
+
266
+ @staticmethod
267
+ def prm_lower_bound_name(prm_name):
268
+ return prm_name + '_lower_bound'
269
+
270
+ @staticmethod
271
+ def prm_upper_bound_name(prm_name):
272
+ return prm_name + '_upper_bound'
273
+
274
+ @staticmethod
275
+ def prm_choices_name(prm_name):
276
+ return prm_name + '_choices'
277
+
278
+ @staticmethod
279
+ def prm_step_name(prm_name):
280
+ return prm_name + '_step'
281
+
282
+
283
+ class ColumnOrderMode(StrEnum):
284
+ """The order rule of the history csv columns."""
285
+ per_category = 'per_category' #: Sort per each object.
286
+ important_first = 'important_first' #: The values of parameters and objectives first.
287
+
288
+
289
+ ColumnOrderModeStr: TypeAlias = Literal['per_category', 'important_first']
290
+
291
+
292
+ class DuplicatedColumnNameError(Exception):
293
+ """:meta private:"""
294
+
295
+
296
+ class NoDuplicateDict(dict):
297
+ def update(self, m: dict, /, **kwargs):
298
+ for key_ in m.keys():
299
+ if key_ in self.keys():
300
+ raise DuplicatedColumnNameError(
301
+ _(
302
+ en_message='The name `{name}` is duplicated. '
303
+ 'Please use another name.',
304
+ jp_message='名前 「{name}」 が重複しています。'
305
+ '別の名前を使ってください。',
306
+ name=key_,
307
+ )
308
+ )
309
+
310
+ super().update(m, **kwargs)
311
+
312
+
313
+ class ColumnManager:
314
+ """:meta private:"""
315
+
316
+ parameters: TrialInput
317
+ y_names: list[str]
318
+ c_names: list[str]
319
+ column_dtypes: dict[str, type]
320
+ meta_columns: list[str]
321
+
322
+ @staticmethod
323
+ def columns_to_keep_even_if_nan():
324
+ return [
325
+ 'messages',
326
+ ]
327
+
328
+ def initialize(
329
+ self,
330
+ parameters: TrialInput,
331
+ y_names,
332
+ c_names,
333
+ additional_data: dict,
334
+ column_order_mode: str = ColumnOrderMode.per_category,
335
+ ):
336
+ self.parameters = parameters
337
+ self.y_names = y_names
338
+ self.c_names = c_names
339
+ self.set_full_sorted_column_information(
340
+ additional_data=additional_data,
341
+ column_order_mode=column_order_mode,
342
+ )
343
+
344
+ def set_full_sorted_column_information(
345
+ self,
346
+ extra_parameters: TrialInput = None,
347
+ extra_y_names: list[str] = None,
348
+ extra_c_names: list[str] = None,
349
+ additional_data: dict = None,
350
+ column_order_mode: str = ColumnOrderMode.per_category,
351
+ ):
352
+ extra_parameters = extra_parameters or TrialInput()
353
+ extra_y_names = extra_y_names or []
354
+ extra_c_names = extra_c_names or []
355
+
356
+ # column name になるので重複は許されない
357
+ column_dtypes: dict = NoDuplicateDict()
358
+ meta_columns: list = []
359
+ column_dtypes_later: dict = NoDuplicateDict()
360
+ meta_columns_later: list = []
361
+
362
+ if column_order_mode == ColumnOrderMode.per_category:
363
+ target_cds: dict = column_dtypes
364
+ target_mcs: list = meta_columns
365
+ elif column_order_mode == ColumnOrderMode.important_first:
366
+ target_cds: dict = column_dtypes_later
367
+ target_mcs: list = meta_columns_later
368
+ else:
369
+ assert False, f'Unknown {column_order_mode=}'
370
+
371
+ # noinspection PyUnresolvedReferences
372
+ keys = Record.__dataclass_fields__.copy().keys()
373
+ for key in keys:
374
+ # Note:
375
+ # as_df() で空欄になりうるカラムには
376
+ # Nan や '' を許容する dtype を指定すること
377
+ # 例えば、 trial に int を指定してはいけない
378
+ #
379
+ # Note:
380
+ # pandas は column_dtypes に str を受け付けない
381
+ # (object にキャストされる模様)
382
+
383
+ if key == 'x':
384
+ for prm_name in self.parameters.keys():
385
+
386
+ param = self.parameters[prm_name]
387
+
388
+ if isinstance(param, NumericParameter):
389
+ # important
390
+ column_dtypes.update({prm_name: float})
391
+ meta_columns.append('prm.num.value')
392
+
393
+ # later
394
+ f = CorrespondingColumnNameRuler.prm_lower_bound_name
395
+ target_cds.update({f(prm_name): float})
396
+ target_mcs.append('prm.num.lower_bound')
397
+
398
+ f = CorrespondingColumnNameRuler.prm_upper_bound_name
399
+ target_cds.update({f(prm_name): float})
400
+ target_mcs.append('prm.num.upper_bound')
401
+
402
+ f = CorrespondingColumnNameRuler.prm_step_name
403
+ target_cds.update({f(prm_name): float})
404
+ target_mcs.append('prm.num.step')
405
+
406
+ elif isinstance(param, CategoricalParameter):
407
+ # important
408
+ column_dtypes.update({prm_name: object})
409
+ meta_columns.append('prm.cat.value')
410
+
411
+ # later
412
+ f = CorrespondingColumnNameRuler.prm_choices_name
413
+ target_cds.update({f(prm_name): object})
414
+ target_mcs.append('prm.cat.choices')
415
+
416
+ else:
417
+ raise NotImplementedError
418
+
419
+ for extra_prm_name, extra_param in extra_parameters.items():
420
+
421
+ if isinstance(extra_param, NumericParameter):
422
+ # later
423
+ target_cds.update({extra_prm_name: float})
424
+ target_mcs.append('')
425
+
426
+ f = CorrespondingColumnNameRuler.prm_lower_bound_name
427
+ target_cds.update({f(extra_prm_name): object})
428
+ target_mcs.append('')
429
+
430
+ f = CorrespondingColumnNameRuler.prm_upper_bound_name
431
+ target_cds.update({f(extra_prm_name): object})
432
+ target_mcs.append('')
433
+
434
+ elif isinstance(extra_param, CategoricalParameter):
435
+ target_cds.update({extra_prm_name: object})
436
+ target_mcs.append('')
437
+
438
+ f = CorrespondingColumnNameRuler.prm_choices_name
439
+ target_cds.update({f(extra_prm_name): object})
440
+ target_mcs.append('')
441
+
442
+ else:
443
+ raise NotImplementedError
444
+
445
+ elif key == 'y':
446
+ f = CorrespondingColumnNameRuler.direction_name
447
+ for name in self.y_names:
448
+ # important
449
+ column_dtypes.update({name: float})
450
+ meta_columns.append('obj')
451
+
452
+ # later
453
+ target_cds.update({f(name): object}) # str | float
454
+ target_mcs.append(f('obj'))
455
+
456
+ for name in extra_y_names:
457
+ # later
458
+ target_cds.update({name: float})
459
+ target_mcs.append('')
460
+
461
+ # later
462
+ target_cds.update({f(name): object}) # str | float
463
+ target_mcs.append('')
464
+
465
+ elif key == 'c':
466
+
467
+ for name in self.c_names:
468
+ # important
469
+ column_dtypes.update({name: float})
470
+ meta_columns.append('cns')
471
+
472
+ for name in extra_c_names:
473
+ # later
474
+ target_cds.update({name: float})
475
+ target_mcs.append('')
476
+
477
+ # additional_data を入れる
478
+ elif key == self._get_additional_data_column():
479
+ # important
480
+ column_dtypes.update({key: object})
481
+ meta_columns.append(json.dumps(additional_data or dict()))
482
+
483
+ elif key in (
484
+ 'feasibility',
485
+ 'optimality',
486
+ 'sub_sampling',
487
+ 'sub_fidelity_name',
488
+ ):
489
+ # important
490
+ column_dtypes.update({key: object})
491
+ meta_columns.append('')
492
+
493
+ else:
494
+ # later
495
+ target_cds.update({key: object})
496
+ target_mcs.append('')
497
+
498
+ column_dtypes.update(column_dtypes_later)
499
+ meta_columns.extend(meta_columns_later)
500
+
501
+ self.column_dtypes = dict(**column_dtypes)
502
+ self.meta_columns = meta_columns
503
+
504
+ @staticmethod
505
+ def _get_additional_data_column():
506
+ return 'trial'
507
+
508
+ @classmethod
509
+ def _get_additional_data(cls, columns, meta_columns) -> dict:
510
+ for column, meta_column in zip(columns, meta_columns):
511
+ if column == cls._get_additional_data_column():
512
+ if meta_column:
513
+ return json.loads(meta_column)
514
+ else:
515
+ return json.loads('{}')
516
+ else:
517
+ raise RuntimeError(f'"{cls._get_additional_data_column()}" is not found in given columns.')
518
+
519
+ @staticmethod
520
+ def _filter_columns(meta_column, columns, meta_columns) -> list[str]:
521
+ out = []
522
+ assert len(columns) == len(meta_columns), f'{len(columns)=} and {len(meta_columns)=}'
523
+
524
+ for i, (column_, meta_column_) in enumerate(zip(columns, meta_columns)):
525
+ if meta_column_ == meta_column:
526
+ out.append(column_)
527
+ return out
528
+
529
+ @classmethod
530
+ def _filter_prm_names(cls, columns, meta_columns) -> list[str]:
531
+ return (
532
+ cls._filter_columns('prm.num.value', columns, meta_columns)
533
+ + cls._filter_columns('prm.cat.value', columns, meta_columns)
534
+ )
535
+
536
+ def filter_columns(self, meta_column) -> list[str]:
537
+ columns = list(self.column_dtypes.keys())
538
+ return self._filter_columns(meta_column, columns, self.meta_columns)
539
+
540
+ def get_prm_names(self) -> list[str]:
541
+ return (
542
+ self.filter_columns('prm.num.value')
543
+ + self.filter_columns('prm.cat.value')
544
+ )
545
+
546
+ def get_obj_names(self) -> list[str]:
547
+ return self.filter_columns('obj')
548
+
549
+ def get_cns_names(self) -> list[str]:
550
+ return self.filter_columns('cns')
551
+
552
+ @staticmethod
553
+ def _is_numerical_parameter(prm_name, columns):
554
+ prm_lb_name = CorrespondingColumnNameRuler.prm_lower_bound_name(prm_name)
555
+ return prm_lb_name in columns
556
+
557
+ @staticmethod
558
+ def _is_categorical_parameter(prm_name, columns):
559
+ prm_choices_name = CorrespondingColumnNameRuler.prm_choices_name(prm_name)
560
+ return prm_choices_name in columns
561
+
562
+ def is_numerical_parameter(self, prm_name) -> bool:
563
+ return self._is_numerical_parameter(prm_name, tuple(self.column_dtypes.keys()))
564
+
565
+ def is_categorical_parameter(self, prm_name) -> bool:
566
+ return self._is_categorical_parameter(prm_name, tuple(self.column_dtypes.keys()))
567
+
568
+ @staticmethod
569
+ def _get_parameter(prm_name: str, df: pd.DataFrame) -> Parameter:
570
+ if ColumnManager._is_numerical_parameter(prm_name, df.columns):
571
+ out = NumericParameter()
572
+ out.name = prm_name
573
+ out.value = float(df[prm_name].dropna().values[-1])
574
+
575
+ # lower_bound
576
+ key = CorrespondingColumnNameRuler.prm_lower_bound_name(prm_name)
577
+ if key in df.columns:
578
+ out.lower_bound = float(df[key].dropna().values[-1])
579
+ else:
580
+ out.lower_bound = None
581
+
582
+ # upper bound
583
+ key = CorrespondingColumnNameRuler.prm_upper_bound_name(prm_name)
584
+ if key in df.columns:
585
+ out.upper_bound = float(df[key].dropna().values[-1])
586
+ else:
587
+ out.upper_bound = None
588
+
589
+ # step
590
+ key = CorrespondingColumnNameRuler.prm_step_name(prm_name)
591
+ if key in df.columns:
592
+ out.step = float(df[key].dropna().values[-1])
593
+ else:
594
+ out.step = None
595
+
596
+ elif ColumnManager._is_categorical_parameter(prm_name, df.columns):
597
+ out = CategoricalParameter()
598
+ out.name = prm_name
599
+ out.value = str(df[prm_name].dropna().values[-1])
600
+ out.choices = df[CorrespondingColumnNameRuler.prm_choices_name(prm_name)].dropna().values[-1]
601
+
602
+ else:
603
+ raise NotImplementedError
604
+
605
+ return out
606
+
607
+ @staticmethod
608
+ def _reconvert_objects(df: pd.DataFrame, meta_columns: list[str]):
609
+ for column, meta_column in zip(df.columns, meta_columns):
610
+ # messages は df の段階で _RECORD_MESSAGE_DELIMITER
611
+ # separated な str なのでここで restore してはいけない
612
+
613
+ # choices list は csv を経由することで str になるので restore
614
+ if meta_column == 'prm.cat.choices':
615
+ df[column] = [ast.literal_eval(d) for d in df[column]]
616
+
617
+ @staticmethod
618
+ def _get_sub_fidelity_names(df: pd.DataFrame) -> list[str]:
619
+
620
+ if 'sub_fidelity_name' not in df.columns:
621
+ return [MAIN_FIDELITY_NAME]
622
+
623
+ else:
624
+ return np.unique(df['sub_fidelity_name'].values).tolist()
625
+
626
+
627
+ _RECORD_MESSAGE_DELIMITER = ' | '
628
+
629
+
630
+ @dataclasses.dataclass
631
+ class Record:
632
+ """:meta private:"""
633
+
634
+ # x, y, c のみ特殊で、データの展開や関連情報の
635
+ # 列への展開を必要とするが、他の field は
636
+ # ここに定義すればよい
637
+
638
+ trial: int = None
639
+ trial_id: int = None
640
+ sub_sampling: SubSampling | None = None
641
+ sub_fidelity_name: str = None
642
+ fidelity: Fidelity = None
643
+ x: TrialInput = dataclasses.field(default_factory=TrialInput)
644
+ y: TrialOutput = dataclasses.field(default_factory=TrialOutput)
645
+ c: TrialConstraintOutput = dataclasses.field(default_factory=TrialConstraintOutput)
646
+ state: TrialState = TrialState.undefined
647
+ datetime_start: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
648
+ datetime_end: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
649
+ messages: list = dataclasses.field(default_factory=list)
650
+ hypervolume: float | None = None
651
+ feasibility: bool | None = None
652
+ optimality: bool | None = None
653
+
654
+ def as_df(self, dtypes: dict = None):
655
+
656
+ # noinspection PyUnresolvedReferences
657
+ keys = self.__dataclass_fields__.copy().keys()
658
+ d = {key: getattr(self, key) for key in keys if getattr(self, key) is not None}
659
+
660
+ x: TrialInput = d.pop('x')
661
+ y: TrialOutput = d.pop('y')
662
+ c: TrialConstraintOutput = d.pop('c')
663
+
664
+ # prm
665
+ for prm_name, param in x.items():
666
+ d.update({prm_name: param.value})
667
+ if isinstance(param, NumericParameter):
668
+ f = CorrespondingColumnNameRuler.prm_lower_bound_name
669
+ d.update({f(prm_name): param.lower_bound})
670
+ f = CorrespondingColumnNameRuler.prm_upper_bound_name
671
+ d.update({f(prm_name): param.upper_bound})
672
+ f = CorrespondingColumnNameRuler.prm_step_name
673
+ d.update({f(prm_name): param.step})
674
+ elif isinstance(param, CategoricalParameter):
675
+ f = CorrespondingColumnNameRuler.prm_choices_name
676
+ d.update({f(prm_name): param.choices})
677
+ else:
678
+ raise NotImplementedError
679
+
680
+ # messages to str
681
+ messages_str = _RECORD_MESSAGE_DELIMITER.join(d['messages'])
682
+ d.update({'messages': messages_str})
683
+
684
+ d.update(**{k: v.value for k, v in y.items()})
685
+ d.update(**{f'{k}_direction': v.direction for k, v in y.items()})
686
+ d.update(**{k: v.value for k, v in c.items()})
687
+
688
+ df = pd.DataFrame(
689
+ {k: [v] for k, v in d.items()},
690
+ columns=tuple(dtypes.keys())
691
+ )
692
+
693
+ if dtypes:
694
+ df = df.astype(dtypes)
695
+
696
+ return df
697
+
698
+ @staticmethod
699
+ def get_state_str_from_series(row: pd.Series):
700
+ state: TrialState = TrialState.undefined
701
+ if 'state' in row:
702
+ state = row['state']
703
+ return state
704
+
705
+
706
+ class EntireDependentValuesCalculator:
707
+ """:meta private:"""
708
+
709
+ def __init__(
710
+ self,
711
+ records: Records,
712
+ equality_filters: dict,
713
+ entire_df: pd.DataFrame,
714
+ ):
715
+
716
+ self.records = records
717
+ self.equality_filters = equality_filters
718
+ self.entire_df: pd.DataFrame = entire_df
719
+ self.partial_df: pd.DataFrame = get_partial_df(entire_df, equality_filters)
720
+
721
+ assert self.records.df_wrapper.lock.locked()
722
+
723
+ # get column names
724
+ obj_names = self.records.column_manager.get_obj_names()
725
+ f = CorrespondingColumnNameRuler.direction_name
726
+ obj_direction_names = [f(name) for name in obj_names]
727
+
728
+ # get values
729
+ all_obj_values = self.partial_df[obj_names].values
730
+ all_obj_directions = self.partial_df[obj_direction_names].values
731
+ feasibility = self.partial_df['feasibility']
732
+
733
+ # convert values as minimization problem
734
+ y_internal = np.empty(all_obj_values.shape)
735
+ for i, (obj_values, obj_directions) \
736
+ in enumerate(zip(all_obj_values.T, all_obj_directions.T)):
737
+ y_internal[:, i] = np.array(
738
+ list(
739
+ map(
740
+ lambda args: Objective._convert(*args),
741
+ zip(obj_values, obj_directions)
742
+ )
743
+ )
744
+ )
745
+
746
+ self.partial_y_internal = y_internal
747
+ self.partial_feasibility = feasibility
748
+
749
+ def update_optimality(self):
750
+
751
+ assert self.records.df_wrapper.lock.locked()
752
+
753
+ # calc optimality
754
+ optimality = calc_optimality(
755
+ self.partial_y_internal,
756
+ self.partial_feasibility,
757
+ )
758
+
759
+ # update
760
+ self.partial_df.loc[:, 'optimality'] = optimality
761
+
762
+ def update_hypervolume(self):
763
+
764
+ assert self.records.df_wrapper.lock.locked()
765
+
766
+ # calc hypervolume
767
+ hv_values = calc_hypervolume(
768
+ self.partial_y_internal,
769
+ self.partial_feasibility,
770
+ ref_point='nadir-up-to-the-point',
771
+ )
772
+
773
+ # update
774
+ self.partial_df.loc[:, 'hypervolume'] = hv_values
775
+
776
+ def update_trial_number(self):
777
+
778
+ assert self.records.df_wrapper.lock.locked()
779
+
780
+ # calc trial
781
+ trial_number = 1 + np.arange(len(self.partial_df)).astype(int)
782
+
783
+ # update
784
+ self.partial_df.loc[:, 'trial'] = trial_number
785
+
786
+
787
+ class Records:
788
+ """:meta private:
789
+
790
+ 最適化の試行全体の情報を格納するモデルクラス
791
+ """
792
+ df_wrapper: DataFrameWrapper
793
+ column_manager: ColumnManager
794
+
795
+ def __init__(self):
796
+ self.df_wrapper = DataFrameWrapper(pd.DataFrame())
797
+ self.column_manager = ColumnManager()
798
+ self.loaded_meta_columns = None
799
+ self.loaded_df = None
800
+
801
+ def __str__(self):
802
+ return self.df_wrapper.__str__()
803
+
804
+ def __len__(self):
805
+ return len(self.df_wrapper)
806
+
807
+ def initialize(self):
808
+ with self.df_wrapper.lock:
809
+ # 新しく始まる場合に備えカラムを設定
810
+ # load の場合はあとで上書きされる
811
+ df = pd.DataFrame([], columns=list(self.column_manager.column_dtypes.keys()))
812
+ self.df_wrapper.set_df(df)
813
+
814
+ def load(self, path: str):
815
+
816
+ for encoding in (ENCODING, 'utf-8'):
817
+ try:
818
+ with open(path, 'r', encoding=encoding, newline='\n') as f:
819
+ reader = csv.reader(f, delimiter=',')
820
+ # load meta_column
821
+ loaded_meta_columns = reader.__next__()
822
+ reader.__next__() # empty line
823
+ # load df from line 3
824
+ loaded_df = pd.read_csv(f, encoding=encoding, header=0)
825
+ break
826
+
827
+ except UnicodeDecodeError:
828
+ continue
829
+
830
+ # df を csv にする過程で失われる list などのオブジェクトを restore
831
+ ColumnManager._reconvert_objects(loaded_df, loaded_meta_columns)
832
+
833
+ # この段階では column_dtypes が setup されていない可能性があるので
834
+ # compatibility check をしない。よって set_df しない。
835
+ self.loaded_meta_columns = loaded_meta_columns
836
+ self.loaded_df = loaded_df
837
+
838
+ def check_problem_compatibility(self):
839
+
840
+ # 読み込んだデータがないのであれば何もしない
841
+ if self.loaded_df is None:
842
+ return
843
+
844
+ # 順番が違ってもいいが、
845
+ # 構成に変更がないこと。
846
+ # ただし obj は減っていてもいい。
847
+ loaded_columns, loaded_meta_columns = self.loaded_df.columns, self.loaded_meta_columns
848
+
849
+ # prm_names が過不足ないか
850
+ loaded_prm_names = set(
851
+ self.column_manager._filter_prm_names(
852
+ loaded_columns, loaded_meta_columns
853
+ )
854
+ )
855
+ prm_names = set(self.column_manager.get_prm_names())
856
+ if not (len(loaded_prm_names - prm_names) == len(prm_names - loaded_prm_names) == 0):
857
+ raise RuntimeError('Incompatible parameter setting.')
858
+
859
+ # obj_names が増えていないか
860
+ loaded_obj_names = set(self.column_manager._filter_columns('obj', loaded_columns, loaded_meta_columns))
861
+ obj_names = set(self.column_manager.get_obj_names())
862
+ if len(obj_names - loaded_obj_names) > 0:
863
+ raise RuntimeError('Incompatible objective setting.')
864
+
865
+ # cns_names が過不足ないか
866
+ # TODO: cns の上下限は変更されてはならない。
867
+ loaded_cns_names = set(self.column_manager._filter_columns('cns', loaded_columns, loaded_meta_columns))
868
+ cns_names = set(self.column_manager.get_cns_names())
869
+ if not (len(loaded_cns_names - cns_names) == len(cns_names - loaded_cns_names) == 0):
870
+ raise RuntimeError('Incompatible constraint setting.')
871
+
872
+ def reinitialize_record_with_loaded_data(self, column_order_mode: str = ColumnOrderMode.per_category):
873
+
874
+ # 読み込んだデータがないのであれば何もしない
875
+ if self.loaded_df is None:
876
+ return
877
+
878
+ loaded_columns, loaded_meta_columns = self.loaded_df.columns, self.loaded_meta_columns
879
+ loaded_prm_names = set(self.column_manager._filter_prm_names(loaded_columns, loaded_meta_columns))
880
+ loaded_obj_names = set(self.column_manager._filter_columns('obj', loaded_columns, loaded_meta_columns))
881
+ loaded_cns_names = set(self.column_manager._filter_columns('cns', loaded_columns, loaded_meta_columns))
882
+
883
+ # loaded df に存在するが Record に存在しないカラムを Record に追加
884
+ extra_parameters = {}
885
+ extra_y_names = []
886
+ extra_c_names = []
887
+ for l_col, l_meta in zip(loaded_columns, loaded_meta_columns):
888
+
889
+ # 現在の Record に含まれないならば
890
+ if l_col not in self.column_manager.column_dtypes.keys():
891
+
892
+ # それが prm_name ならば
893
+ if l_col in loaded_prm_names:
894
+
895
+ # それが Categorical ならば
896
+ if CorrespondingColumnNameRuler.prm_choices_name(l_col) in loaded_columns:
897
+ param = CategoricalParameter()
898
+ param.name = l_col
899
+ param.value = ''
900
+ param.choices = []
901
+
902
+ # それが Numeric ならば
903
+ elif CorrespondingColumnNameRuler.prm_lower_bound_name(l_col) in loaded_columns:
904
+ param = NumericParameter()
905
+ param.name = l_col
906
+ param.value = np.nan
907
+ param.lower_bound = np.nan
908
+ param.upper_bound = np.nan
909
+
910
+ else:
911
+ raise NotImplementedError
912
+
913
+ extra_parameters.update({l_col: param})
914
+
915
+ # obj_name ならば
916
+ elif l_col in loaded_obj_names:
917
+ extra_y_names.append(l_col)
918
+
919
+ # cns_name ならば
920
+ elif l_col in loaded_cns_names:
921
+ extra_c_names.append(l_col)
922
+
923
+ # additional data を取得
924
+ a_data = self.column_manager._get_additional_data(loaded_columns, loaded_meta_columns)
925
+
926
+ self.column_manager.set_full_sorted_column_information(
927
+ extra_parameters=extra_parameters,
928
+ extra_y_names=extra_y_names,
929
+ extra_c_names=extra_c_names,
930
+ additional_data=a_data,
931
+ column_order_mode=column_order_mode,
932
+ )
933
+
934
+ # worker に影響しないように loaded_df のコピーを作成
935
+ df: pd.DataFrame = self.loaded_df.copy()
936
+
937
+ # loaded df に存在しないが Record に存在するカラムを追加
938
+ for col in self.column_manager.column_dtypes.keys():
939
+ if col not in df.columns:
940
+ # column ごとの default 値を追加
941
+ if col == 'sub_fidelity_name':
942
+ df[col] = MAIN_FIDELITY_NAME
943
+ else:
944
+ df[col] = np.nan
945
+
946
+ # column_dtypes を設定
947
+ # 与える column_dtypes のほうが多い場合
948
+ # エラーになるので余分なものを削除
949
+ # 与える column_dtypes が少ない分には
950
+ # (pandas としては) 問題ない
951
+ dtypes = {k: v for k, v in self.column_manager.column_dtypes.items() if k in self.loaded_df.columns}
952
+ df = df.astype(dtypes)
953
+
954
+ # 並べ替え
955
+ df = df[list(self.column_manager.column_dtypes.keys())].astype(self.column_manager.column_dtypes)
956
+
957
+ # OK なので読み込んだデータを set_df する
958
+ self.df_wrapper.set_df(df)
959
+
960
+ def remove_nan_columns(
961
+ self, df, meta_columns, columns_to_keep: str | list[str] = None
962
+ ) -> tuple[pd.DataFrame, tuple[str]]:
963
+ """
964
+
965
+ Args:
966
+ df:
967
+ meta_columns:
968
+ columns_to_keep: Allowing these columns to all NaN values.
969
+
970
+ Returns:
971
+ Removed DataFrame and corresponding meta_columns.
972
+
973
+ """
974
+
975
+ df = df.replace('', None)
976
+
977
+ nan_columns = df.isna().all(axis=0)
978
+ if columns_to_keep is None:
979
+ columns_to_keep = self.column_manager.columns_to_keep_even_if_nan()
980
+ nan_columns[columns_to_keep] = False
981
+
982
+ fdf = df.loc[:, ~nan_columns]
983
+ f_meta_columns = (np.array(meta_columns)[~nan_columns]).tolist()
984
+
985
+ return fdf, f_meta_columns
986
+
987
+ def save(self, path: str):
988
+
989
+ # filter NaN columns
990
+ df, meta_columns = self.remove_nan_columns(
991
+ self.df_wrapper.get_df(), self.column_manager.meta_columns,
992
+ )
993
+
994
+ try:
995
+ with open(path, 'w', encoding=ENCODING) as f:
996
+ writer = csv.writer(f, delimiter=',', lineterminator="\n")
997
+ # write meta_columns
998
+ writer.writerow(meta_columns)
999
+ writer.writerow([''] * len(meta_columns)) # empty line
1000
+ # write df from line 3
1001
+ df.to_csv(f, index=False, encoding=ENCODING, lineterminator='\n')
1002
+ except PermissionError:
1003
+ logger.warning(
1004
+ _(
1005
+ en_message='History csv file ({path}) is in use and cannot be written to. '
1006
+ 'Please free this file before exiting the program, '
1007
+ 'otherwise history data will be lost.',
1008
+ jp_message='履歴のCSVファイル({path})が使用中のため書き込みできません。'
1009
+ 'プログラムを終了する前にこのファイルを閉じてください。'
1010
+ 'そうしない場合、履歴データが失われます。',
1011
+ path=path,
1012
+ )
1013
+ )
1014
+
1015
+ def append(self, record: Record) -> pd.Series:
1016
+
1017
+ # get row
1018
+ row = record.as_df(dtypes=self.column_manager.column_dtypes)
1019
+
1020
+ # concat
1021
+ dfw = self.df_wrapper
1022
+
1023
+ # append
1024
+ with dfw.lock:
1025
+
1026
+ df = dfw.get_df()
1027
+
1028
+ if len(df) == 0:
1029
+ # ここで空のカラムを削除しては
1030
+ # データの並びがおかしくなるケースが出る
1031
+ new_df = row
1032
+ else:
1033
+ # pandas の型推定の仕様変更対策で
1034
+ # 空のカラムは削除する
1035
+ row.dropna(axis=1, inplace=True, how='all')
1036
+
1037
+ new_df = pd.concat(
1038
+ [df, row],
1039
+ axis=0,
1040
+ ignore_index=True,
1041
+ )
1042
+
1043
+ # calc entire-dependent values
1044
+ # must be in with block to keep
1045
+ # the entire data compatibility
1046
+ # during processing.
1047
+ self.update_entire_dependent_values(new_df)
1048
+
1049
+ dfw.set_df(new_df)
1050
+
1051
+ # postprocess after recording で使うために
1052
+ # 計算済み最終行を返す
1053
+ return new_df.iloc[-1]
1054
+
1055
+ def update_entire_dependent_values(self, processing_df: pd.DataFrame):
1056
+
1057
+ with self.df_wrapper.lock_if_not_locked:
1058
+
1059
+ # check trial_id is filled
1060
+ trial_processed = False
1061
+ if processing_df['trial_id'].notna().all():
1062
+ id_to_n: dict = {tid: i + 1 for i, tid
1063
+ in enumerate(processing_df['trial_id'].unique())}
1064
+ processing_df['trial'] = processing_df['trial_id'].map(id_to_n)
1065
+ trial_processed = True
1066
+
1067
+ # update main fidelity
1068
+ equality_filters = MAIN_FILTER
1069
+ mgr = EntireDependentValuesCalculator(
1070
+ self,
1071
+ equality_filters,
1072
+ processing_df,
1073
+ )
1074
+ mgr.update_optimality()
1075
+ mgr.update_hypervolume()
1076
+ if not trial_processed:
1077
+ mgr.update_trial_number() # per_fidelity
1078
+ pdf = mgr.partial_df
1079
+ apply_partial_df(df=processing_df, partial_df=pdf, equality_filters=equality_filters)
1080
+
1081
+ # update sub fidelity
1082
+ sub_fidelity_names: list = np.unique(processing_df['sub_fidelity_name']).tolist()
1083
+ if MAIN_FIDELITY_NAME in sub_fidelity_names:
1084
+ sub_fidelity_names.remove(MAIN_FIDELITY_NAME)
1085
+ for sub_fidelity_name in sub_fidelity_names:
1086
+ equality_filters = {'sub_fidelity_name': sub_fidelity_name}
1087
+ mgr = EntireDependentValuesCalculator(
1088
+ self,
1089
+ equality_filters,
1090
+ processing_df
1091
+ )
1092
+ if not trial_processed:
1093
+ mgr.update_trial_number() # per_fidelity
1094
+ pdf = mgr.partial_df
1095
+ apply_partial_df(df=processing_df, partial_df=pdf, equality_filters=equality_filters)
1096
+
1097
+
1098
+ class History:
1099
+ """最適化の試行の履歴を管理します。"""
1100
+ _records: Records
1101
+ prm_names: list[str]
1102
+ obj_names: list[str]
1103
+ cns_names: list[str]
1104
+ sub_fidelity_names: list[str]
1105
+ is_restart: bool
1106
+ additional_data: dict
1107
+
1108
+ path: str
1109
+ """The existing or destination CSV path.
1110
+
1111
+ If not specified, the CSV file is saved in the format
1112
+ "pyfemtet.opt_%Y%m%d_%H%M%S.csv"
1113
+ when the optimization process starts.
1114
+ """
1115
+
1116
+ MAIN_FILTER = MAIN_FILTER
1117
+
1118
+ def __init__(self):
1119
+ self._records = Records()
1120
+ self.path: str | None = None
1121
+ self._finalized: bool = False
1122
+ self.is_restart = False
1123
+ self.additional_data = dict(version=pyfemtet.__version__)
1124
+ self.column_order_mode: ColumnOrderMode | ColumnOrderModeStr = ColumnOrderMode.per_category
1125
+
1126
+ def __str__(self):
1127
+ return self._records.__str__()
1128
+
1129
+ def __enter__(self):
1130
+ self._records.df_wrapper.start_dask()
1131
+
1132
+ def __exit__(self, exc_type, exc_val, exc_tb):
1133
+ self._records.df_wrapper.end_dask()
1134
+
1135
+ def load_csv(self, path, with_finalize=False):
1136
+ """:meta private:"""
1137
+
1138
+ self.is_restart = True
1139
+ self.path = path
1140
+ self._records.load(self.path)
1141
+
1142
+ if with_finalize:
1143
+ self._finalize_from_loaded_data()
1144
+
1145
+ def _finalize_from_loaded_data(self):
1146
+ assert self.is_restart
1147
+
1148
+ if not self._finalized:
1149
+
1150
+ df = self._records.loaded_df
1151
+ meta_columns = self._records.loaded_meta_columns
1152
+
1153
+ self.prm_names = ColumnManager._filter_prm_names(df.columns, meta_columns)
1154
+ self.obj_names = ColumnManager._filter_columns('obj', df.columns, meta_columns)
1155
+ self.cns_names = ColumnManager._filter_columns('cns', df.columns, meta_columns)
1156
+ self.sub_fidelity_names = ColumnManager._get_sub_fidelity_names(df)
1157
+ self.additional_data = ColumnManager._get_additional_data(df.columns, meta_columns)
1158
+
1159
+ parameters: TrialInput = {}
1160
+ for prm_name in self.prm_names:
1161
+ param = ColumnManager._get_parameter(prm_name, df)
1162
+ parameters.update({prm_name: param})
1163
+
1164
+ self.finalize(
1165
+ parameters,
1166
+ self.obj_names,
1167
+ self.cns_names,
1168
+ self.sub_fidelity_names,
1169
+ self.additional_data,
1170
+ )
1171
+
1172
+ def finalize(
1173
+ self,
1174
+ parameters: TrialInput,
1175
+ obj_names,
1176
+ cns_names,
1177
+ sub_fidelity_names,
1178
+ additional_data,
1179
+ ):
1180
+ """:meta private:"""
1181
+
1182
+ self.prm_names = list(parameters.keys())
1183
+ self.obj_names = list(obj_names)
1184
+ self.cns_names = list(cns_names)
1185
+ self.sub_fidelity_names = list(sub_fidelity_names)
1186
+ self.additional_data.update(additional_data)
1187
+
1188
+ if not self._finalized:
1189
+ # ここで column_dtypes が決定する
1190
+ self._records.column_manager.initialize(
1191
+ parameters, self.obj_names, self.cns_names, self.additional_data, self.column_order_mode
1192
+ )
1193
+
1194
+ # initialize
1195
+ self._records.initialize()
1196
+
1197
+ if self.path is None:
1198
+ self.path = datetime.datetime.now().strftime("pyfemtet.opt_%Y%m%d_%H%M%S.csv")
1199
+
1200
+ # load
1201
+ if os.path.isfile(self.path):
1202
+ self.load_csv(self.path)
1203
+ self._records.check_problem_compatibility()
1204
+ self._records.reinitialize_record_with_loaded_data(self.column_order_mode)
1205
+
1206
+ self._finalized = True
1207
+
1208
+ def get_df(self, equality_filters: dict = None) -> pd.DataFrame:
1209
+ """Returns the optimization history.
1210
+
1211
+ Args:
1212
+ equality_filters (dict, optional):
1213
+ The {column: value} というフォーマットの
1214
+ matching filter.
1215
+
1216
+ Returns: The optimization history.
1217
+
1218
+ """
1219
+ return self._records.df_wrapper.get_df(equality_filters)
1220
+
1221
+ @staticmethod
1222
+ def get_trial_name(trial=None, fidelity=None, sub_sampling=None, row: pd.Series = None):
1223
+ if row is not None:
1224
+ assert not math.isnan(row['trial'])
1225
+ trial = row['trial']
1226
+ fidelity = row['fidelity'] if not math.isnan(row['fidelity']) else None
1227
+ sub_sampling = row['sub_sampling'] if not math.isnan(row['sub_sampling']) else None
1228
+
1229
+ name_parts = ['trial']
1230
+ if fidelity is not None:
1231
+ fid = str(fidelity)
1232
+ if fid != MAIN_FIDELITY_NAME:
1233
+ name_parts.append(fid)
1234
+
1235
+ name_parts.append(str(trial))
1236
+
1237
+ if sub_sampling is not None:
1238
+ name_parts.append(str(sub_sampling))
1239
+
1240
+ trial_name = '_'.join(name_parts)
1241
+
1242
+ return trial_name
1243
+
1244
+ def recording(self, fem: AbstractFEMInterface):
1245
+ """:meta private:"""
1246
+
1247
+ # noinspection PyMethodParameters
1248
+ class RecordContext:
1249
+
1250
+ def __init__(self_):
1251
+ self_.record = Record()
1252
+ self_.record_as_df = None
1253
+
1254
+ def __enter__(self_):
1255
+ return self_.record
1256
+
1257
+ def append(self_):
1258
+ self_.record.datetime_end = self_.record.datetime_end \
1259
+ if self_.record.datetime_end is not None \
1260
+ else datetime.datetime.now()
1261
+ return self._records.append(self_.record)
1262
+
1263
+ @staticmethod
1264
+ def postprocess_after_recording(row):
1265
+
1266
+ client = get_client()
1267
+
1268
+ trial_name = self.get_trial_name(row=row)
1269
+
1270
+ # FIXME: メインフィデリティだけでなく、FEM に
1271
+ # 対応するフィデリティ又はサブサンプリングのみ
1272
+ # フィルタした情報を提供するようにする。
1273
+ # フィデリティの話は現在解析を実行している opt が
1274
+ # 必要なので、recording メソッドの引数に
1275
+ # それを追加する
1276
+ df = self.get_df(equality_filters=self.MAIN_FILTER)
1277
+
1278
+ if client is not None:
1279
+ client.run_on_scheduler(
1280
+ fem._postprocess_after_recording,
1281
+ trial_name=trial_name,
1282
+ df=df,
1283
+ **(fem._create_postprocess_args()),
1284
+ )
1285
+
1286
+ else:
1287
+ fem._postprocess_after_recording(
1288
+ dask_scheduler=None,
1289
+ trial_name=trial_name,
1290
+ df=df,
1291
+ **(fem._create_postprocess_args())
1292
+ )
1293
+
1294
+ def __exit__(self_, exc_type, exc_val, exc_tb):
1295
+
1296
+ row: pd.Series | None = None
1297
+
1298
+ # record feasibility
1299
+ # skipped -> None (empty)
1300
+ # succeeded -> True
1301
+ # else -> False
1302
+ if self_.record.state == TrialState.skipped:
1303
+ self_.record.feasibility = None
1304
+
1305
+ elif self_.record.state == TrialState.succeeded:
1306
+ self_.record.feasibility = True
1307
+
1308
+ else:
1309
+ self_.record.feasibility = False
1310
+
1311
+ # append
1312
+ if exc_type is None:
1313
+ row = self_.append()
1314
+ # 1st argument of issubclass cannot be None
1315
+ elif issubclass(exc_type, ExceptionDuringOptimization):
1316
+ row = self_.append()
1317
+
1318
+ # if append is succeeded,
1319
+ # do fem.post_processing
1320
+ if row is not None:
1321
+ self_.postprocess_after_recording(row)
1322
+
1323
+ # save history
1324
+ client = get_client()
1325
+ if client is None:
1326
+ self.save()
1327
+
1328
+ return RecordContext()
1329
+
1330
+ def save(self):
1331
+ """Export the optimization history.
1332
+
1333
+ The destination path is :class:`History.path`.
1334
+ """
1335
+ self._records.save(self.path)
1336
+
1337
+ def _create_optuna_study_for_visualization(self):
1338
+ import optuna
1339
+
1340
+ # create study
1341
+ kwargs: dict[str, ...] = dict(
1342
+ # storage='sqlite:///' + os.path.basename(self.path) + '_dummy.db',
1343
+ sampler=None, pruner=None, study_name='dummy',
1344
+ )
1345
+ if len(self.obj_names) == 1:
1346
+ kwargs.update(dict(direction='minimize'))
1347
+ else:
1348
+ kwargs.update(dict(directions=['minimize']*len(self.obj_names)))
1349
+ study = optuna.create_study(**kwargs)
1350
+
1351
+ # add trial to study
1352
+ df = self.get_df(equality_filters=MAIN_FILTER)
1353
+
1354
+ for i, row in df.iterrows():
1355
+
1356
+ # trial
1357
+ trial_kwargs: dict = dict()
1358
+
1359
+ # state
1360
+ state_str = Record.get_state_str_from_series(row)
1361
+ if state_str != TrialState.succeeded:
1362
+ continue
1363
+ state = optuna.trial.TrialState.COMPLETE
1364
+ trial_kwargs.update(dict(state=state))
1365
+
1366
+ # params
1367
+ params = {prm_name: row[prm_name] for prm_name in self.prm_names}
1368
+ trial_kwargs.update(dict(params=params))
1369
+
1370
+ # distribution
1371
+ distributions: dict[str, optuna.distributions.BaseDistribution] = dict()
1372
+ for prm_name in params.keys():
1373
+
1374
+ # float
1375
+ if self._records.column_manager.is_numerical_parameter(prm_name):
1376
+ lb_name = CorrespondingColumnNameRuler.prm_lower_bound_name(prm_name)
1377
+ ub_name = CorrespondingColumnNameRuler.prm_upper_bound_name(prm_name)
1378
+ dist = optuna.distributions.FloatDistribution(
1379
+ low=row[lb_name],
1380
+ high=row[ub_name],
1381
+ )
1382
+
1383
+ # categorical
1384
+ elif self._records.column_manager.is_categorical_parameter(prm_name):
1385
+ choices_name = CorrespondingColumnNameRuler.prm_choices_name(prm_name)
1386
+ dist = optuna.distributions.CategoricalDistribution(
1387
+ choices=row[choices_name]
1388
+ )
1389
+
1390
+ else:
1391
+ raise NotImplementedError
1392
+
1393
+ distributions.update(
1394
+ {prm_name: dist}
1395
+ )
1396
+ trial_kwargs.update(dict(distributions=distributions))
1397
+
1398
+ # objective
1399
+ if len(self.obj_names) == 1:
1400
+ trial_kwargs.update(dict(value=row[self.obj_names].values[0]))
1401
+ else:
1402
+ trial_kwargs.update(dict(values=row[self.obj_names].values))
1403
+
1404
+ # add to study
1405
+ trial = optuna.create_trial(**trial_kwargs)
1406
+ study.add_trial(trial)
1407
+
1408
+ return study
1409
+
1410
+ def is_numerical_parameter(self, prm_name: str) -> bool:
1411
+ """:meta private:"""
1412
+ return self._records.column_manager.is_numerical_parameter(prm_name)
1413
+
1414
+ def is_categorical_parameter(self, prm_name) -> bool:
1415
+ """:meta private:"""
1416
+ return self._records.column_manager.is_categorical_parameter(prm_name)