{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.11"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":39763,"databundleVersionId":11756775,"sourceType":"competition"}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":70.256882,"end_time":"2025-04-11T19:19:18.239740","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-04-11T19:18:07.982858","version":"2.6.0"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"00f735f342334bd2adb683298e82dd21":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"01a6e5302d2c4b32a835e430e3c4920d":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_9140941a6f37457d83ef80d7818b85c4","IPY_MODEL_9000b8faa8a544aab75ad27e7ab75406","IPY_MODEL_028ed2e4d6294034a996d62a0e67a73f"],"layout":"IPY_MODEL_bf6f4c4da0a14cf68902a28db0cb0c56","tabbable":null,"tooltip":null}},"028ed2e4d6294034a996d62a0e67a73f":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_62322e10aadb4b11b493e95eb9f6c69f","placeholder":"​","style":"IPY_MODEL_3ca88fef6f2c4338b956a78d65f6b1cf","tabbable":null,"tooltip":null,"value":" 2/2 [00:06&lt;00:00,  3.48s/it]"}},"04cdd40836da4a7da6b5ef85d98c477d":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"","description":"","description_allow_html":false,"layout":"IPY_MODEL_8d44ddf52f1d4527a22d6a0ddf3553f4","max":2,"min":0,"orientation":"horizontal","style":"IPY_MODEL_8960506cbf9341f29baafc914e61c806","tabbable":null,"tooltip":null,"value":2}},"06a9d427eeba4b96a2caace69d1919bb":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_c8408bbae689480d9ae0bf8b6b6fb2d1","IPY_MODEL_04cdd40836da4a7da6b5ef85d98c477d","IPY_MODEL_8c534f44daca4c3dab2199e90d7e5702"],"layout":"IPY_MODEL_1243d2c8ada74bedadfafec70d6c4462","tabbable":null,"tooltip":null}},"1243d2c8ada74bedadfafec70d6c4462":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":"hidden","width":null}},"1377ecc4110541d38ae4e38ee9a56627":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_4ab27616958a4d8e8fce990bb7a6f3e2","placeholder":"​","style":"IPY_MODEL_46151e008e09483c9a25f53dd98b9052","tabbable":null,"tooltip":null,"value":"Loading CurveVel_B: 100%"}},"1a1080d80e894b2393aacb2e827c177a":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_1377ecc4110541d38ae4e38ee9a56627","IPY_MODEL_c3eee77076ef474c84f9c116e1d855f8","IPY_MODEL_6665582c41d34fbdb70f0f0b4b6668df"],"layout":"IPY_MODEL_2165e3c8d3114d35bcdd00de04d88d61","tabbable":null,"tooltip":null}},"1bf026fa8b7d4d4d8ac3c91e1da0b281":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_9f54e5573aba4887ab838bff6148f8d8","placeholder":"​","style":"IPY_MODEL_f38ccf0243c64127a9de8f898e1a3293","tabbable":null,"tooltip":null,"value":" 2/2 [00:07&lt;00:00,  3.64s/it]"}},"2165e3c8d3114d35bcdd00de04d88d61":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":"hidden","width":null}},"25efc165bd304b82b32f1adaa258fa01":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_408413376aa24201a47532df5908792c","placeholder":"​","style":"IPY_MODEL_534312f948b549d3a599108380a73d8c","tabbable":null,"tooltip":null,"value":" 2/2 [00:09&lt;00:00,  4.37s/it]"}},"2a912c60fae34bd0ad7093aad448ebf6":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"34af11b265ba4886aaebec27d1178baf":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"38415db926bf42348454baaa81ebb28c":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"3877c86a6f6144a6b52c92759c8eac13":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_82020f16fce74357964a975e5e34bb24","placeholder":"​","style":"IPY_MODEL_784b1ea4e4e9488595eb152eadec7ea3","tabbable":null,"tooltip":null,"value":"Loading FlatVel_A: 100%"}},"399e7625339a4a29932ea5d9dbac104d":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_f6a39c88fe0341cbb61533abb1e54fdc","placeholder":"​","style":"IPY_MODEL_76c03833879c4a4dab905e59ef731323","tabbable":null,"tooltip":null,"value":"Loading Style_A: 100%"}},"3c627dccb4fa4c1fb47da1bb2f313dca":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":"hidden","width":null}},"3ca88fef6f2c4338b956a78d65f6b1cf":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"408413376aa24201a47532df5908792c":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"46151e008e09483c9a25f53dd98b9052":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"4ab27616958a4d8e8fce990bb7a6f3e2":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"534312f948b549d3a599108380a73d8c":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"54794cb7baca4e3c956a06b754fb3254":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_399e7625339a4a29932ea5d9dbac104d","IPY_MODEL_5afa05a629c94559956925e47436dbe5","IPY_MODEL_8528855913a64234994f7d890b23d1d2"],"layout":"IPY_MODEL_3c627dccb4fa4c1fb47da1bb2f313dca","tabbable":null,"tooltip":null}},"596a6abafeee4690a089cc46aeb342a1":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":"hidden","width":null}},"5afa05a629c94559956925e47436dbe5":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"","description":"","description_allow_html":false,"layout":"IPY_MODEL_f0754ac7fb2546b080d8eb4d10d46554","max":2,"min":0,"orientation":"horizontal","style":"IPY_MODEL_74a9216d4e194759a639edb20217fd4a","tabbable":null,"tooltip":null,"value":2}},"5f2f170fb0f14802bffa119758334bdc":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_c11b265b6141487aa34039b178aeb6a5","placeholder":"​","style":"IPY_MODEL_8a06ccfdcf7842498346b82c00ad4c30","tabbable":null,"tooltip":null,"value":"Loading Style_B: 100%"}},"62322e10aadb4b11b493e95eb9f6c69f":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"6665582c41d34fbdb70f0f0b4b6668df":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_cacaaacf8880465eab6b92534346d742","placeholder":"​","style":"IPY_MODEL_f0cf512db534475f9d975e2aefbbd7cf","tabbable":null,"tooltip":null,"value":" 2/2 [00:06&lt;00:00,  3.43s/it]"}},"68a3f39b93a9449eb635dbcacdbb79bb":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"74a9216d4e194759a639edb20217fd4a":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"76c03833879c4a4dab905e59ef731323":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"76eb2757426f41d09e41970aced2f432":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"784b1ea4e4e9488595eb152eadec7ea3":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"7b3b4009f19c412b8b4a0efdb84fcd3e":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"80f4f4c32dbc488baccc0d20deab91cd":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"","description":"","description_allow_html":false,"layout":"IPY_MODEL_c7274e479e3045c09fe534437d4089e5","max":2,"min":0,"orientation":"horizontal","style":"IPY_MODEL_b64f219546e54f9a994393c8b5ec48fb","tabbable":null,"tooltip":null,"value":2}},"82020f16fce74357964a975e5e34bb24":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"82ed525460164eafbdb707d1686f8a57":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"834a3262bc0840998ccdff3c99b1166a":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"8528855913a64234994f7d890b23d1d2":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_82ed525460164eafbdb707d1686f8a57","placeholder":"​","style":"IPY_MODEL_76eb2757426f41d09e41970aced2f432","tabbable":null,"tooltip":null,"value":" 2/2 [00:06&lt;00:00,  3.50s/it]"}},"86b2417bad7944ba8f81c9562ca4cd17":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":"hidden","width":null}},"873ea4b2899f4f49801821f0c623602e":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"8960506cbf9341f29baafc914e61c806":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"8a06ccfdcf7842498346b82c00ad4c30":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"8c534f44daca4c3dab2199e90d7e5702":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_873ea4b2899f4f49801821f0c623602e","placeholder":"​","style":"IPY_MODEL_d77e2613b067412faa0ee7776b36ff0b","tabbable":null,"tooltip":null,"value":" 2/2 [00:07&lt;00:00,  3.67s/it]"}},"8d44ddf52f1d4527a22d6a0ddf3553f4":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"9000b8faa8a544aab75ad27e7ab75406":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"","description":"","description_allow_html":false,"layout":"IPY_MODEL_2a912c60fae34bd0ad7093aad448ebf6","max":2,"min":0,"orientation":"horizontal","style":"IPY_MODEL_94620af000154100afe7bb0960134a0a","tabbable":null,"tooltip":null,"value":2}},"9140941a6f37457d83ef80d7818b85c4":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_00f735f342334bd2adb683298e82dd21","placeholder":"​","style":"IPY_MODEL_834a3262bc0840998ccdff3c99b1166a","tabbable":null,"tooltip":null,"value":"Loading CurveVel_A: 100%"}},"94620af000154100afe7bb0960134a0a":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"9b2218abb676461e8bab9bfbb9cb5b53":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"9f54e5573aba4887ab838bff6148f8d8":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"b64f219546e54f9a994393c8b5ec48fb":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"b69939dbbdb44832aa6f53352d5edcb6":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"","description":"","description_allow_html":false,"layout":"IPY_MODEL_7b3b4009f19c412b8b4a0efdb84fcd3e","max":2,"min":0,"orientation":"horizontal","style":"IPY_MODEL_9b2218abb676461e8bab9bfbb9cb5b53","tabbable":null,"tooltip":null,"value":2}},"b70113b0a9a44bb19fe0a8152a1e7a50":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"bf6f4c4da0a14cf68902a28db0cb0c56":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":"hidden","width":null}},"c11b265b6141487aa34039b178aeb6a5":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"c3eee77076ef474c84f9c116e1d855f8":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"","description":"","description_allow_html":false,"layout":"IPY_MODEL_b70113b0a9a44bb19fe0a8152a1e7a50","max":2,"min":0,"orientation":"horizontal","style":"IPY_MODEL_38415db926bf42348454baaa81ebb28c","tabbable":null,"tooltip":null,"value":2}},"c7274e479e3045c09fe534437d4089e5":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"c8408bbae689480d9ae0bf8b6b6fb2d1":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_34af11b265ba4886aaebec27d1178baf","placeholder":"​","style":"IPY_MODEL_68a3f39b93a9449eb635dbcacdbb79bb","tabbable":null,"tooltip":null,"value":"Loading FlatVel_B: 100%"}},"cacaaacf8880465eab6b92534346d742":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"d77e2613b067412faa0ee7776b36ff0b":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"ec1ed986593444c2af874a7506ac5501":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_5f2f170fb0f14802bffa119758334bdc","IPY_MODEL_b69939dbbdb44832aa6f53352d5edcb6","IPY_MODEL_1bf026fa8b7d4d4d8ac3c91e1da0b281"],"layout":"IPY_MODEL_596a6abafeee4690a089cc46aeb342a1","tabbable":null,"tooltip":null}},"f0754ac7fb2546b080d8eb4d10d46554":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"f0cf512db534475f9d975e2aefbbd7cf":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"f38ccf0243c64127a9de8f898e1a3293":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"f53eb4d3c8644d738f4e35fcbbe0d2c9":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_3877c86a6f6144a6b52c92759c8eac13","IPY_MODEL_80f4f4c32dbc488baccc0d20deab91cd","IPY_MODEL_25efc165bd304b82b32f1adaa258fa01"],"layout":"IPY_MODEL_86b2417bad7944ba8f81c9562ca4cd17","tabbable":null,"tooltip":null}},"f6a39c88fe0341cbb61533abb1e54fdc":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}}},"version_major":2,"version_minor":0}}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"658dbf56","cell_type":"code","source":"# CODE CELL 1: Imports and Setup\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nimport glob\nfrom sklearn.model_selection import train_test_split\nfrom tqdm.notebook import tqdm\nimport random\nimport gc\nimport matplotlib.cm as cm\nfrom scipy.ndimage import gaussian_filter\nfrom skimage.metrics import structural_similarity as ssim # Import SSIM\n\n# Set seeds for reproducibility - crucial for academic reporting\ndef set_seed(seed=42):\n    \"\"\"Sets random seeds for major libraries for reproducibility.\"\"\"\n    random.seed(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    # Optional: Configure TensorFlow to be deterministic (may impact performance)\n    # tf.config.experimental.enable_op_determinism() \n\nset_seed(42)\n\n# Enable mixed precision for potentially faster training on compatible GPUs\n# Note: May slightly affect numerical precision, monitor validation loss closely.\ntry:\n    mixed_precision = tf.keras.mixed_precision\n    policy = mixed_precision.Policy('mixed_float16')\n    mixed_precision.set_global_policy(policy)\n    print(\"Mixed precision enabled.\")\nexcept Exception as e:\n    print(f\"Could not enable mixed precision: {e}\")\n\n# Data directories (adjust path if necessary)\nINPUT_DIR = '/kaggle/input/waveform-inversion'\nTRAIN_DIR = os.path.join(INPUT_DIR, 'train_samples')\nTEST_DIR = os.path.join(INPUT_DIR, 'test')\n\n# Display available training datasets\nprint(\"\\nAvailable training datasets:\")\nif os.path.exists(TRAIN_DIR):\n    for item in sorted(os.listdir(TRAIN_DIR)):\n        if os.path.isdir(os.path.join(TRAIN_DIR, item)):\n            print(f\"- {item}\")\nelse:\n    print(f\"Training directory not found: {TRAIN_DIR}\")","metadata":{"_cell_guid":"275ebffc-dc1f-468d-995f-b95b6900590b","_uuid":"ad31cde8-2238-43d4-af07-5eba2cc98d16","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:24:13.809941Z","iopub.execute_input":"2025-05-08T07:24:13.810132Z","iopub.status.idle":"2025-05-08T07:24:28.222446Z","shell.execute_reply.started":"2025-05-08T07:24:13.810115Z","shell.execute_reply":"2025-05-08T07:24:28.221673Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":15.922248,"end_time":"2025-04-11T19:18:28.129088","exception":false,"start_time":"2025-04-11T19:18:12.206840","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"c0cf1867","cell_type":"markdown","source":"### Data Loading Functions","metadata":{"_cell_guid":"58435ad5-0e29-408c-8bc9-f0ca2962fde8","_uuid":"8946450c-dd21-4c01-a524-1003ec7828fa","collapsed":false,"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.005349,"end_time":"2025-04-11T19:18:28.140440","exception":false,"start_time":"2025-04-11T19:18:28.135091","status":"completed"},"tags":[]}},{"id":"ecc852cb","cell_type":"code","source":"# CODE CELL 2: Data Loading Functions\ndef load_training_data(dataset_family, sample_limit=None):\n    \"\"\"Loads training data (seismic gathers and velocity models) for a specific family.\"\"\"\n    data_dir = os.path.join(TRAIN_DIR, dataset_family)\n    data_path = os.path.join(data_dir, 'data', '*.npy')\n    model_path = os.path.join(data_dir, 'model', '*.npy')\n    \n    data_files = sorted(glob.glob(data_path))\n    model_files = sorted(glob.glob(model_path))\n    \n    if not data_files or not model_files:\n        print(f\"Warning: No data or model files found in {dataset_family} under expected paths.\")\n        return None, None\n        \n    if len(data_files) != len(model_files):\n        print(f\"Warning: Mismatch in number of data ({len(data_files)}) and model ({len(model_files)}) files in {dataset_family}. Skipping.\")\n        return None, None\n        \n    X_list = []\n    y_list = []\n    \n    file_pairs = list(zip(data_files, model_files))\n    if sample_limit and sample_limit < len(file_pairs):\n        # Select a deterministic subset if sample_limit is used\n        indices = np.linspace(0, len(file_pairs) - 1, sample_limit, dtype=int)\n        file_pairs = [file_pairs[i] for i in indices]\n        print(f\"Limiting to {len(file_pairs)} samples for {dataset_family}.\")\n\n    print(f\"Loading data from {len(file_pairs)} file pairs in {dataset_family}...\")\n    for data_file, model_file in tqdm(file_pairs, desc=f\"Loading {dataset_family}\", leave=False):\n        try:\n            # Consider adding error handling for corrupted files\n            X = np.load(data_file)\n            y = np.load(model_file)\n            \n            # Basic validation of shapes (optional but recommended)\n            if X.ndim != 4 or y.ndim != 3:\n                 print(f\"Warning: Unexpected dimensions in {data_file} (X shape: {X.shape}) or {model_file} (y shape: {y.shape}). Skipping file pair.\")\n                 continue\n\n            X_list.append(X)\n            y_list.append(y)\n        except Exception as e:\n            print(f\"Error loading file pair: {data_file}, {model_file}. Error: {e}\")\n\n    if not X_list or not y_list:\n        print(f\"No valid data loaded for {dataset_family}.\")\n        return None, None\n\n    X = np.concatenate(X_list, axis=0).astype(np.float32) # Ensure float32 for TF\n    y = np.concatenate(y_list, axis=0).astype(np.float32) # Ensure float32 for TF\n    \n    # Free memory\n    del X_list, y_list\n    gc.collect()\n    \n    return X, y\n\ndef load_test_data():\n    \"\"\"Loads test seismic data keyed by object ID (oid).\"\"\"\n    test_files = sorted(glob.glob(os.path.join(TEST_DIR, '*.npy')))\n    if not test_files:\n        print(f\"Warning: No test files found in {TEST_DIR}\")\n        return {}, []\n        \n    test_data = {}\n    oids = []\n    \n    print(f\"Loading {len(test_files)} test files...\")\n    for test_file in tqdm(test_files, desc=\"Loading test data\", leave=False):\n        try:\n            oid = os.path.basename(test_file).split('.')[0]\n            data = np.load(test_file).astype(np.float32) # Ensure float32\n             # Basic validation\n            if data.ndim != 4:\n                print(f\"Warning: Unexpected dimensions in test file {test_file} (shape: {data.shape}). Skipping.\")\n                continue\n            test_data[oid] = data\n            oids.append(oid)\n        except Exception as e:\n            print(f\"Error loading test file: {test_file}. Error: {e}\")\n            \n    return test_data, oids","metadata":{"_cell_guid":"858249c0-f860-41d8-a27b-9dc6291873d1","_uuid":"5a7648c2-fb1d-454a-8e47-ccfa04a3f8e8","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:24:28.224065Z","iopub.execute_input":"2025-05-08T07:24:28.224730Z","iopub.status.idle":"2025-05-08T07:24:28.234456Z","shell.execute_reply.started":"2025-05-08T07:24:28.224710Z","shell.execute_reply":"2025-05-08T07:24:28.233606Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.018215,"end_time":"2025-04-11T19:18:28.164133","exception":false,"start_time":"2025-04-11T19:18:28.145918","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"4bf2dc58","cell_type":"markdown","source":"### Data Exploration and Preprocessing Functions","metadata":{"_cell_guid":"235ffb7b-e653-433e-abc6-0af025b54b0d","_uuid":"7d4395e0-d409-486c-a6e0-02db63e05441","collapsed":false,"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.005303,"end_time":"2025-04-11T19:18:28.175120","exception":false,"start_time":"2025-04-11T19:18:28.169817","status":"completed"},"tags":[]}},{"id":"44e37877","cell_type":"code","source":"# CODE CELL 3: Visualization and Preprocessing Functions\ndef plot_seismic_and_velocity(seismic_data, velocity_map, sample_idx=0, title_prefix=\"\"):\n    \"\"\"Plots a seismic shot gather and the corresponding velocity map.\"\"\"\n    if sample_idx >= seismic_data.shape[0]:\n        print(f\"Error: sample_idx {sample_idx} out of bounds for data with shape {seismic_data.shape}\")\n        return\n        \n    fig, axes = plt.subplots(1, 2, figsize=(18, 7))\n    \n    # --- Seismic Data Plot ---\n    num_sources = seismic_data.shape[1]\n    source_idx = num_sources // 2  # Visualize the middle source gather\n    seismic_gather = seismic_data[sample_idx, source_idx]\n    \n    # Determine appropriate color limits for seismic data\n    clim_abs = np.percentile(np.abs(seismic_gather), 98) # Use 98th percentile for robust limits\n    \n    im_seismic = axes[0].imshow(seismic_gather, aspect='auto', cmap='seismic', \n                                vmin=-clim_abs, vmax=clim_abs)\n    axes[0].set_title(f'{title_prefix}Seismic Data (Sample {sample_idx}, Source {source_idx})')\n    axes[0].set_xlabel('Receiver Index')\n    axes[0].set_ylabel('Time Sample Index')\n    plt.colorbar(im_seismic, ax=axes[0], label='Amplitude')\n    \n    # --- Velocity Map Plot ---\n    vel_map_sample = velocity_map[sample_idx]\n    im_velocity = axes[1].imshow(vel_map_sample, cmap='viridis', aspect='auto',\n                                 vmin=np.min(vel_map_sample), vmax=np.max(vel_map_sample)) # Use actual range\n    axes[1].set_title(f'{title_prefix}Ground Truth Velocity Map (Sample {sample_idx})')\n    axes[1].set_xlabel('Horizontal Position Index (X)')\n    axes[1].set_ylabel('Depth Position Index (Y)')\n    plt.colorbar(im_velocity, ax=axes[1], label='Velocity (m/s)')\n    \n    plt.tight_layout()\n    plt.show()\n\ndef preprocess_seismic_batch(seismic_batch):\n    \"\"\"\n    Applies sample-wise normalization to a batch of seismic data.\n    Input shape: (batch, num_sources, time_steps, num_receivers)\n    Output shape: (batch, num_sources, time_steps, num_receivers)\n    \"\"\"\n    batch_size, num_sources, time_steps, num_receivers = seismic_batch.shape\n    # Process in float32 for precision during normalization\n    processed_batch = seismic_batch.astype(np.float32) \n    \n    epsilon = 1e-8 # Small constant for numerical stability\n\n    for i in range(batch_size):\n        for j in range(num_sources):\n            data_slice = processed_batch[i, j] # Shape (time_steps, num_receivers)\n            mean = np.mean(data_slice)\n            std = np.std(data_slice)\n            if std > epsilon:\n                processed_batch[i, j] = (data_slice - mean) / std\n            else:\n                # Handle constant or near-constant slices (avoid division by zero)\n                processed_batch[i, j] = data_slice - mean # Just center it\n                \n    return processed_batch\n\ndef apply_output_constraints(velocity_maps, min_velocity=1500.0, smoothing_sigma=0.5):\n    \"\"\"\n    Applies physics-based constraints to predicted velocity maps:\n    1. Enforces minimum velocity.\n    2. Applies gentle Gaussian smoothing.\n    Input shape: (batch, height, width)\n    Output shape: (batch, height, width)\n    \"\"\"\n    constrained_maps = velocity_maps.copy()\n    \n    # 1. Minimum Velocity Constraint\n    constrained_maps = np.maximum(constrained_maps, min_velocity)\n    \n    # 2. Gaussian Smoothing (applied per map in the batch)\n    if smoothing_sigma is not None and smoothing_sigma > 0:\n        for i in range(constrained_maps.shape[0]):\n            constrained_maps[i] = gaussian_filter(constrained_maps[i], sigma=smoothing_sigma)\n            \n    return constrained_maps","metadata":{"_cell_guid":"2409ca17-26b6-4ebf-a89a-3f67dd804a63","_uuid":"ce21a244-bc0c-487b-8696-74d245b64ae3","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:24:28.235290Z","iopub.execute_input":"2025-05-08T07:24:28.235551Z","iopub.status.idle":"2025-05-08T07:24:28.273587Z","shell.execute_reply.started":"2025-05-08T07:24:28.235528Z","shell.execute_reply":"2025-05-08T07:24:28.273040Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.016795,"end_time":"2025-04-11T19:18:28.197279","exception":false,"start_time":"2025-04-11T19:18:28.180484","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"fab49e7d","cell_type":"markdown","source":"## Deep Learning Architectures for Inversion\n\nWe employ deep convolutional neural networks (CNNs) designed for image-to-image translation tasks, adapted for the specifics of seismic inversion. The goal is to learn the mapping from the multi-source seismic data (input \"image\" with dimensions time x receivers x sources) to the subsurface velocity map (output image with dimensions depth x horizontal distance).","metadata":{"_cell_guid":"33cb4ae4-6fca-49a1-bfcf-350ee1933358","_uuid":"38bb6c58-3def-4c29-b023-64f88b7d8cef","collapsed":false,"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.005394,"end_time":"2025-04-11T19:18:28.208317","exception":false,"start_time":"2025-04-11T19:18:28.202923","status":"completed"},"tags":[]}},{"id":"e4baa1c6","cell_type":"code","source":"# CODE CELL 4: Physics-Guided U-Net Implementation\ndef build_physics_guided_unet(input_shape, output_shape, base_filters=64, depth=4, \n                                kernel_size=3, pool_size=(2, 2), \n                                use_batch_norm=True, dropout_rate=0.0, # Added dropout option\n                                smoothness_weight=0.05, min_velocity=1500.0):\n    \"\"\"Builds a U-Net model with configurable depth and physics-guided components.\"\"\"\n    \n    # --- Loss Function Definition ---\n    def physics_guided_loss(y_true, y_pred):\n        \"\"\"Custom loss: MAE + Smoothness Regularization.\"\"\"\n        # Ensure calculations are in float32 for stability if using mixed precision\n        y_true = tf.cast(y_true, tf.float32)\n        y_pred = tf.cast(y_pred, tf.float32)\n        \n        mae_loss = tf.reduce_mean(tf.abs(y_true - y_pred))\n        \n        # Calculate spatial gradients (difference between adjacent pixels)\n        # Note: tf.image.image_gradients returns derivatives in y, x order\n        dy, dx = tf.image.image_gradients(y_pred) \n        \n        # L1 norm of gradients encourages sparsity (sharp edges) while promoting smoothness\n        smoothness_loss = tf.reduce_mean(tf.abs(dy)) + tf.reduce_mean(tf.abs(dx))\n        \n        total_loss = mae_loss + smoothness_weight * smoothness_loss\n        return total_loss\n\n    # --- U-Net Building Blocks ---\n    def conv_block(inputs, filters, kernel_size=kernel_size, padding='same', \n                   use_batch_norm=use_batch_norm, activation='leaky_relu', dropout=dropout_rate):\n        \"\"\"Standard convolutional block for U-Net.\"\"\"\n        x = layers.Conv2D(filters, kernel_size, padding=padding, kernel_initializer='he_normal')(inputs)\n        if use_batch_norm:\n            x = layers.BatchNormalization()(x)\n        if activation == 'leaky_relu':\n             x = layers.LeakyReLU(alpha=0.2)(x)\n        else:\n             x = layers.Activation(activation)(x)\n        if dropout > 0:\n             x = layers.Dropout(dropout)(x)\n\n        x = layers.Conv2D(filters, kernel_size, padding=padding, kernel_initializer='he_normal')(x)\n        if use_batch_norm:\n            x = layers.BatchNormalization()(x)\n        if activation == 'leaky_relu':\n             x = layers.LeakyReLU(alpha=0.2)(x)\n        else:\n             x = layers.Activation(activation)(x)\n        if dropout > 0:\n             x = layers.Dropout(dropout)(x)\n        return x\n\n    def encoder_block(inputs, filters):\n        \"\"\"Encoder block: ConvBlock + MaxPooling.\"\"\"\n        conv = conv_block(inputs, filters)\n        pool = layers.MaxPooling2D(pool_size=pool_size)(conv)\n        return conv, pool # Return conv output for skip connection\n\n    def decoder_block(inputs, skip_connection, filters):\n        \"\"\"Decoder block: Upsample -> Concatenate -> ConvBlock.\"\"\"\n        # Upsampling using Transposed Convolution\n        up = layers.Conv2DTranspose(filters, kernel_size=pool_size, strides=pool_size, padding='same')(inputs)\n        \n        # Concatenate skip connection\n        # Ensure skip connection shape matches upsampled shape if padding='valid' was used in encoder\n        concat = layers.Concatenate()([up, skip_connection])\n        \n        conv = conv_block(concat, filters)\n        return conv\n\n    # --- Model Construction ---\n    inputs = keras.Input(shape=input_shape)\n    \n    # Placeholder for potential future physics-informed input layers\n    current_layer = inputs \n    \n    skip_connections = []\n    filters = base_filters\n\n    # Encoder Path\n    print(\"Building Encoder...\")\n    for _ in range(depth):\n        print(f\"  Depth {_ + 1}, Filters: {filters}\")\n        conv, pool = encoder_block(current_layer, filters)\n        skip_connections.append(conv)\n        current_layer = pool\n        filters *= 2 \n        \n    # Bottleneck\n    print(f\"Building Bottleneck, Filters: {filters}\")\n    bridge = conv_block(current_layer, filters)\n    current_layer = bridge\n    \n    # Decoder Path\n    print(\"Building Decoder...\")\n    for i in range(depth):\n        filters //= 2\n        print(f\"  Depth {depth - i}, Filters: {filters}\")\n        skip = skip_connections[depth - 1 - i]\n        current_layer = decoder_block(current_layer, skip, filters)\n\n    # Output Layer\n    outputs = layers.Conv2D(1, (1, 1), padding='same', activation='linear')(current_layer) \n    # Reshape to match target velocity map shape (H, W)\n    # The Conv2D output might have shape (H, W, 1), so Reshape removes the channel dim.\n    outputs = layers.Reshape(output_shape, name=\"raw_output\")(outputs) \n    \n    # Apply Minimum Velocity Constraint\n    # Using a Lambda layer ensures this constraint is part of the model graph\n    outputs = layers.Lambda(lambda x: tf.maximum(x, min_velocity), name=\"constrained_output\")(outputs)\n    \n    # Define the model\n    model = keras.Model(inputs=inputs, outputs=outputs, name=f\"PhysicsGuided_UNet_Depth{depth}\")\n    \n    # Compile the model\n    optimizer = keras.optimizers.Adam(learning_rate=1e-3) # Initial learning rate\n    # If using mixed precision, wrap the optimizer\n    if mixed_precision.global_policy().name == 'mixed_float16':\n        optimizer = mixed_precision.LossScaleOptimizer(optimizer)\n        \n    model.compile(optimizer=optimizer, \n                  loss=physics_guided_loss, \n                  metrics=[keras.metrics.MeanAbsoluteError(name='mae')]) # Track standard MAE\n\n    return model","metadata":{"_cell_guid":"f2f7f90c-6b88-4dd4-81d1-5c7c4cb0dd80","_uuid":"480ecd53-3bf8-4ca5-8738-8b503ecd4fad","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:04.665890Z","iopub.execute_input":"2025-05-08T07:25:04.666569Z","iopub.status.idle":"2025-05-08T07:25:04.678263Z","shell.execute_reply.started":"2025-05-08T07:25:04.666543Z","shell.execute_reply":"2025-05-08T07:25:04.677606Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.020351,"end_time":"2025-04-11T19:18:28.234153","exception":false,"start_time":"2025-04-11T19:18:28.213802","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"6f3e4e7b","cell_type":"code","source":"# CODE CELL 5: ResNet-style Model Implementation\ndef build_resnet_style_model(input_shape, output_shape, base_filters=64, num_blocks_per_stage=[2, 2, 2, 2],\n                               kernel_size=3, use_batch_norm=True, min_velocity=1500.0):\n    \"\"\"Builds a ResNet-style encoder-decoder model for inversion.\"\"\"\n\n    def residual_block(x, filters, kernel_size=kernel_size, stride=1, \n                       use_batch_norm=use_batch_norm, activation='leaky_relu'):\n        \"\"\"A standard residual block.\"\"\"\n        shortcut = x # Store the input for the shortcut connection\n        \n        # First convolutional layer in the block\n        conv1 = layers.Conv2D(filters, kernel_size, strides=stride, padding='same', kernel_initializer='he_normal')(x)\n        if use_batch_norm:\n            conv1 = layers.BatchNormalization()(conv1)\n        if activation == 'leaky_relu':\n             conv1 = layers.LeakyReLU(alpha=0.2)(conv1)\n        else:\n             conv1 = layers.Activation(activation)(conv1)\n             \n        # Second convolutional layer in the block\n        conv2 = layers.Conv2D(filters, kernel_size, strides=1, padding='same', kernel_initializer='he_normal')(conv1)\n        if use_batch_norm:\n            conv2 = layers.BatchNormalization()(conv2)\n            \n        # Shortcut connection: Add input to the output of the conv layers\n        # If dimensions change (due to stride > 1 or different number of filters), \n        # apply a projection (1x1 conv) to the shortcut.\n        if stride != 1 or shortcut.shape[-1] != filters:\n            shortcut = layers.Conv2D(filters, (1, 1), strides=stride, padding='same', kernel_initializer='he_normal')(shortcut)\n            if use_batch_norm: # Apply BN to shortcut as well if used elsewhere\n                shortcut = layers.BatchNormalization()(shortcut)\n\n        output = layers.add([conv2, shortcut])\n        \n        # Final activation after merging\n        if activation == 'leaky_relu':\n             output = layers.LeakyReLU(alpha=0.2)(output)\n        else:\n             output = layers.Activation(activation)(output)\n        return output\n\n    inputs = keras.Input(shape=input_shape)\n    \n    # --- Initial Convolution ---\n    # Using a larger kernel initially can capture broader features\n    x = layers.Conv2D(base_filters, 7, strides=2, padding='same', kernel_initializer='he_normal')(inputs) \n    if use_batch_norm:\n        x = layers.BatchNormalization()(x)\n    x = layers.LeakyReLU(alpha=0.2)(x)\n    x = layers.MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding='same')(x) # Initial pooling\n\n    # --- Encoder Stages ---\n    filters = base_filters\n    encoder_stages = []\n    print(\"Building ResNet Encoder...\")\n    for i, num_blocks in enumerate(num_blocks_per_stage):\n        print(f\"  Stage {i+1}, Filters: {filters}, Blocks: {num_blocks}\")\n        # Downsample at the start of each stage (except the first, already done)\n        stride = 2 if i > 0 else 1 \n        x = residual_block(x, filters, stride=stride) \n        for _ in range(num_blocks - 1):\n            x = residual_block(x, filters, stride=1)\n        encoder_stages.append(x) # Store for potential skip connections later if needed\n        filters *= 2\n\n    # --- Decoder Stages (Simplified: No skip connections here, focuses on upsampling + residual blocks) ---\n    print(\"Building ResNet Decoder...\")\n    for i in range(len(num_blocks_per_stage) - 1, -1, -1): # Iterate backward through stages\n        filters //= 2\n        num_blocks = num_blocks_per_stage[i]\n        print(f\"  Stage {i+1}, Filters: {filters}, Blocks: {num_blocks}\")\n        # Upsample using Conv2DTranspose\n        x = layers.Conv2DTranspose(filters, kernel_size=(3, 3), strides=2, padding='same')(x)\n        # Apply residual blocks after upsampling\n        for _ in range(num_blocks):\n            x = residual_block(x, filters, stride=1)\n            \n    # --- Final Upsampling and Output ---\n    # Add potentially more ConvTranspose layers if needed to match output size\n    # This depends heavily on the strides used in the encoder\n    # For simplicity, assume final stage output needs one more upsample + final conv\n    \n    # Example: One more transpose conv to potentially restore resolution before final 1x1\n    x = layers.Conv2DTranspose(base_filters // 2, kernel_size=(3, 3), strides=2, padding='same')(x)\n    x = residual_block(x, base_filters // 2, stride=1) # Final residual block\n\n    outputs = layers.Conv2D(1, (1, 1), padding='same', activation='linear')(x)\n    # Adjust output shape if needed, ensure it matches y_train's HxW\n    # This might require careful calculation of padding/strides or an adaptive pooling/upsampling layer\n    \n    # Example: Ensure output has the target spatial dimensions. If not exact, could use resizing.\n    # This is a common challenge in designing encoder-decoders precisely.\n    # We will rely on Reshape, assuming the network learns to produce the correct HxW spatially.\n    outputs = layers.Reshape(output_shape, name=\"raw_output\")(outputs) \n\n    outputs = layers.Lambda(lambda x: tf.maximum(x, min_velocity), name=\"constrained_output\")(outputs)\n    \n    model = keras.Model(inputs=inputs, outputs=outputs, name=\"ResNetStyle_Inverter\")\n    \n    optimizer = keras.optimizers.Adam(learning_rate=1e-3)\n    if mixed_precision.global_policy().name == 'mixed_float16':\n        optimizer = mixed_precision.LossScaleOptimizer(optimizer)\n        \n    # Using standard MAE loss for this model variant\n    model.compile(optimizer=optimizer, loss='mae', metrics=['mae']) \n    \n    return model","metadata":{"_cell_guid":"1e8aafd9-fdfe-4687-a822-21401ed35485","_uuid":"b4bec771-e818-4663-8e4f-a93d82bf1745","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:13.332720Z","iopub.execute_input":"2025-05-08T07:25:13.333006Z","iopub.status.idle":"2025-05-08T07:25:13.345025Z","shell.execute_reply.started":"2025-05-08T07:25:13.332986Z","shell.execute_reply":"2025-05-08T07:25:13.344263Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.03557,"end_time":"2025-04-11T19:18:28.294987","exception":false,"start_time":"2025-04-11T19:18:28.259417","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"27064381","cell_type":"code","source":"# CODE CELL 6: Custom Data Generator with Augmentation\nclass FWIDataGenerator(keras.utils.Sequence):\n    \"\"\"\n    Custom Keras data generator for FWI training.\n    Handles batching, shuffling, preprocessing, and data augmentation.\n    \"\"\"\n    def __init__(self, X, y, batch_size=8, input_shape=(751, 70, 10), output_shape=(101,101), \n                 shuffle=True, augment=True, noise_level_range=(0.01, 0.05), flip_prob=0.5):\n        \"\"\"\n        Initializes the data generator.\n        Args:\n            X: Input seismic data (num_samples, num_sources, time_steps, num_receivers)\n            y: Target velocity models (num_samples, height, width)\n            batch_size: Number of samples per batch\n            input_shape: Expected model input shape (time_steps, num_receivers, num_sources)\n            output_shape: Expected model output shape (height, width) - used for verification\n            shuffle: Whether to shuffle data indices at the end of each epoch\n            augment: Whether to apply data augmentation\n            noise_level_range: Tuple (min, max) for the std deviation of Gaussian noise relative to data range\n            flip_prob: Probability of applying horizontal flip augmentation\n        \"\"\"\n        if X.shape[0] != y.shape[0]:\n             raise ValueError(\"X and y must have the same number of samples.\")\n        if y.shape[1:] != output_shape:\n             print(f\"Warning: y shape {y.shape[1:]} doesn't match expected output_shape {output_shape}\")\n             \n        self.X = X\n        self.y = y\n        self.batch_size = batch_size\n        self.input_shape = input_shape # (time, receivers, sources)\n        self.output_shape = output_shape # (height, width)\n        self.shuffle = shuffle\n        self.augment = augment\n        self.noise_level_range = noise_level_range\n        self.flip_prob = flip_prob\n        \n        self.num_samples = len(self.X)\n        self.indexes = np.arange(self.num_samples)\n        self.on_epoch_end() # Initial shuffle if needed\n\n    def __len__(self):\n        \"\"\"Returns the number of batches per epoch.\"\"\"\n        return int(np.floor(self.num_samples / self.batch_size))\n\n    def __getitem__(self, index):\n        \"\"\"Generates one batch of data.\"\"\"\n        # Generate indexes of the batch\n        start_idx = index * self.batch_size\n        end_idx = (index + 1) * self.batch_size\n        indexes = self.indexes[start_idx:end_idx]\n\n        # Find list of IDs\n        X_batch = self.X[indexes]\n        y_batch = self.y[indexes]\n\n        # Preprocess the seismic data (normalization)\n        X_batch_processed = self.preprocess_batch(X_batch)\n        \n        # Apply augmentation if enabled\n        if self.augment:\n            X_batch_processed, y_batch = self.augment_batch(X_batch_processed, y_batch)\n\n        # Reshape X for model input: (batch, T, R, S)\n        # Original X shape: (batch, S, T, R)\n        # Target shape: (batch, T, R, S) based on input_shape\n        # Need to transpose: axes (0, 2, 3, 1)\n        try:\n             X_batch_final = np.transpose(X_batch_processed, (0, 2, 3, 1))\n             # Verify against self.input_shape\n             if X_batch_final.shape[1:] != self.input_shape:\n                 raise ValueError(f\"Processed X shape {X_batch_final.shape[1:]} != expected input shape {self.input_shape}\")\n        except Exception as e:\n             print(f\"Error during final reshape/transpose of X_batch: {e}\")\n             print(f\"  X_batch_processed shape was: {X_batch_processed.shape}\")\n             # Return empty arrays or re-raise? For now, print and return potentially incorrect shape\n             # This indicates an issue in shape definitions or loading\n             return np.zeros((self.batch_size, *self.input_shape)), np.zeros((self.batch_size, *self.output_shape))\n\n\n        # Ensure y_batch has the correct shape as well\n        if y_batch.shape[1:] != self.output_shape:\n             print(f\"Warning: y_batch shape {y_batch.shape[1:]} != expected output shape {self.output_shape}\")\n\n        return X_batch_final, y_batch\n\n    def on_epoch_end(self):\n        \"\"\"Updates indexes after each epoch.\"\"\"\n        if self.shuffle:\n            np.random.shuffle(self.indexes)\n\n    def preprocess_batch(self, X_batch_raw):\n        \"\"\"Applies sample-wise normalization to the batch.\"\"\"\n        # Uses the standalone function defined earlier\n        return preprocess_seismic_batch(X_batch_raw)\n\n    def augment_batch(self, X_batch, y_batch):\n        \"\"\"Applies random augmentation to the batch.\"\"\"\n        augmented_X = X_batch.copy()\n        augmented_y = y_batch.copy()\n        \n        batch_size = X_batch.shape[0]\n        num_sources = X_batch.shape[1]\n        \n        for i in range(batch_size):\n            # 1. Add Noise (per source)\n            if np.random.rand() > 0.5: # Apply noise ~50% of the time\n                noise_std_factor = np.random.uniform(self.noise_level_range[0], self.noise_level_range[1])\n                for j in range(num_sources):\n                    data_slice = augmented_X[i, j]\n                    signal_std = np.std(data_slice)\n                    noise_std = signal_std * noise_std_factor \n                    noise = np.random.normal(0, noise_std, data_slice.shape).astype(data_slice.dtype)\n                    augmented_X[i, j] += noise\n\n            # 2. Horizontal Flip\n            if np.random.rand() < self.flip_prob:\n                # Flip receivers (last dimension of X before transpose)\n                augmented_X[i] = augmented_X[i, :, :, ::-1] \n                # Flip velocity map horizontally (last dimension)\n                augmented_y[i] = augmented_y[i, :, ::-1]\n                \n        return augmented_X, augmented_y","metadata":{"_cell_guid":"725f1de7-78fc-48a0-ab83-f3b5445b3b97","_uuid":"acd3ad38-0b02-4062-bbc4-0611acb8275b","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:21.201635Z","iopub.execute_input":"2025-05-08T07:25:21.202182Z","iopub.status.idle":"2025-05-08T07:25:21.214064Z","shell.execute_reply.started":"2025-05-08T07:25:21.202156Z","shell.execute_reply":"2025-05-08T07:25:21.213272Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.025248,"end_time":"2025-04-11T19:18:28.350350","exception":false,"start_time":"2025-04-11T19:18:28.325102","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"f5c7619b","cell_type":"code","source":"# CODE CELL 7: Ensemble Model Definition (Conceptual)\ndef define_ensemble_models(input_shape, output_shape):\n    \"\"\"\n    Defines a list of models to be potentially used in an ensemble.\n    Note: This function only *builds* the models; it doesn't train or combine them.\n    \"\"\"\n    models = []\n    \n    print(\"Defining Model 1: Physics-Guided U-Net\")\n    model1 = build_physics_guided_unet(input_shape, output_shape, \n                                       base_filters=64, depth=4, smoothness_weight=0.05) \n    models.append((\"UNet_Physics_D4\", model1))\n    \n    print(\"\\nDefining Model 2: ResNet-style Model\")\n    model2 = build_resnet_style_model(input_shape, output_shape,\n                                      base_filters=64, num_blocks_per_stage=[2, 2, 2, 2])\n    models.append((\"ResNet_2222\", model2))\n    \n    # --- Placeholder for potential additional models ---\n    # print(\"\\nDefining Model 3: Deeper U-Net\")\n    # model3 = build_physics_guided_unet(input_shape, output_shape, \n    #                                    base_filters=32, depth=5, smoothness_weight=0.03) \n    # models.append((\"UNet_Physics_D5_F32\", model3))\n\n    # print(\"\\nDefining Model 4: U-Net without Physics Loss (for comparison)\")\n    # model4 = build_physics_guided_unet(input_shape, output_shape, \n    #                                    base_filters=64, depth=4, smoothness_weight=0.0) # No smoothness term\n    # # Need to recompile model4 with standard MAE loss if smoothness_weight=0 in builder doesn't handle it\n    # # optimizer = keras.optimizers.Adam(learning_rate=1e-3)\n    # # if mixed_precision.global_policy().name == 'mixed_float16':\n    # #     optimizer = mixed_precision.LossScaleOptimizer(optimizer)\n    # # model4.compile(optimizer=optimizer, loss='mae', metrics=['mae'])\n    # models.append((\"UNet_StandardMAE_D4\", model4))\n    \n    print(f\"\\nDefined {len(models)} candidate models for ensemble.\")\n    return models\n\n# Note: Global variables X_train, y_train are no longer needed here, shapes are passed explicitly.","metadata":{"_cell_guid":"237b79ac-3add-482e-8429-4a4e6e3a253e","_uuid":"d37b9b63-ded5-4814-aaf5-f387f6e569c4","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:28.064238Z","iopub.execute_input":"2025-05-08T07:25:28.064490Z","iopub.status.idle":"2025-05-08T07:25:28.069861Z","shell.execute_reply.started":"2025-05-08T07:25:28.064472Z","shell.execute_reply":"2025-05-08T07:25:28.069094Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.012331,"end_time":"2025-04-11T19:18:28.379427","exception":false,"start_time":"2025-04-11T19:18:28.367096","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"7c30677e","cell_type":"code","source":"# CODE CELL 8: Model Training Function\ndef train_model(model, X_train, y_train, X_val, y_val, \n                input_shape, output_shape, # Pass shapes explicitly\n                batch_size=8, epochs=30, model_save_path='best_fwi_model.h5'):\n    \"\"\"\n    Trains the FWI model using custom data generators and callbacks.\n    \n    Args:\n        model: Compiled Keras model to train.\n        X_train, y_train: Training data and labels.\n        X_val, y_val: Validation data and labels.\n        input_shape: Expected model input shape tuple (T, R, S).\n        output_shape: Expected model output shape tuple (H, W).\n        batch_size: Training batch size.\n        epochs: Maximum number of training epochs.\n        model_save_path: Path to save the best model weights.\n        \n    Returns:\n        model: Trained model (best weights restored).\n        history: Keras training history object.\n    \"\"\"\n    print(f\"\\n--- Starting Model Training ---\")\n    print(f\"Model: {model.name}\")\n    print(f\"Training samples: {len(X_train)}, Validation samples: {len(X_val)}\")\n    print(f\"Batch size: {batch_size}, Max Epochs: {epochs}\")\n    print(f\"Input shape: {input_shape}, Output shape: {output_shape}\")\n    \n    # Create Data Generators\n    train_generator = FWIDataGenerator(X_train, y_train, batch_size=batch_size, \n                                     input_shape=input_shape, output_shape=output_shape,\n                                     shuffle=True, augment=True)\n    val_generator = FWIDataGenerator(X_val, y_val, batch_size=batch_size, \n                                   input_shape=input_shape, output_shape=output_shape,\n                                   shuffle=False, augment=False) # No augmentation/shuffle for validation\n\n    # Define Callbacks\n    callbacks = [\n        keras.callbacks.ModelCheckpoint(model_save_path, save_best_only=True, \n                                        monitor='val_loss', mode='min', verbose=1,\n                                        save_weights_only=True), # Save only weights is usually sufficient and faster\n        keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, \n                                          min_lr=1e-6, verbose=1),\n        keras.callbacks.EarlyStopping(monitor='val_loss', patience=10, verbose=1, \n                                      restore_best_weights=True) # Automatically restores best weights\n    ]\n    \n    # Train the model\n    history = model.fit(\n        train_generator,\n        validation_data=val_generator,\n        epochs=epochs,\n        callbacks=callbacks,\n        verbose=1 # Set to 1 for progress bar, 2 for one line per epoch, 0 for silent\n    )\n    \n    # Note: If EarlyStopping restored best weights, no need to load manually.\n    # If save_weights_only=False in ModelCheckpoint, or if not using EarlyStopping's restore_best_weights,\n    # you might need to load the best model explicitly:\n    # print(f\"Loading best weights from {model_save_path}\")\n    # model.load_weights(model_save_path) # Load the best weights saved by ModelCheckpoint\n    \n    print(\"--- Model Training Finished ---\")\n    return model, history","metadata":{"_cell_guid":"dd7c06f9-ffd1-4c51-898e-17d07dbea6dc","_uuid":"39a8c858-9ad3-4aa9-a3d2-eecf0ecea063","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:33.595948Z","iopub.execute_input":"2025-05-08T07:25:33.596729Z","iopub.status.idle":"2025-05-08T07:25:33.603265Z","shell.execute_reply.started":"2025-05-08T07:25:33.596698Z","shell.execute_reply":"2025-05-08T07:25:33.602680Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.014075,"end_time":"2025-04-11T19:18:28.410087","exception":false,"start_time":"2025-04-11T19:18:28.396012","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"65108fb2","cell_type":"code","source":"# CODE CELL 9: Model Evaluation Function\ndef evaluate_model(model, X_eval, y_eval, input_shape, \n                   num_samples_to_plot=3, batch_size_eval=16):\n    \"\"\"\n    Evaluates the model on evaluation data (e.g., validation set) and visualizes results.\n    \n    Args:\n        model: Trained Keras model.\n        X_eval, y_eval: Evaluation data and labels.\n        input_shape: Model's expected input shape (T, R, S).\n        num_samples_to_plot: Number of random samples to visualize.\n        batch_size_eval: Batch size for prediction to manage memory.\n    \"\"\"\n    print(\"\\n--- Starting Model Evaluation ---\")\n    if len(X_eval) == 0:\n        print(\"Evaluation dataset is empty. Skipping evaluation.\")\n        return\n\n    # --- Predict on Evaluation Data ---\n    # Reshape X_eval for prediction: (batch, T, R, S)\n    eval_samples = len(X_eval)\n    try:\n        X_eval_reshaped = np.transpose(X_eval, (0, 2, 3, 1))\n        if X_eval_reshaped.shape[1:] != input_shape:\n             raise ValueError(f\"Evaluation X shape {X_eval_reshaped.shape[1:]} != expected input shape {input_shape}\")\n    except Exception as e:\n         print(f\"Error reshaping X_eval for evaluation: {e}. Aborting evaluation.\")\n         return\n\n    print(f\"Predicting on {eval_samples} evaluation samples...\")\n    y_pred_raw = model.predict(X_eval_reshaped, batch_size=batch_size_eval, verbose=0)\n    \n    # Apply physics constraints (min velocity + smoothing) post-prediction\n    print(\"Applying physics constraints to predictions...\")\n    y_pred_constrained = apply_output_constraints(y_pred_raw, min_velocity=1500.0, smoothing_sigma=0.5)\n    \n    # --- Calculate Metrics ---\n    print(\"Calculating metrics...\")\n    # Ensure y_eval is float32 for consistent calculations\n    y_eval_f32 = y_eval.astype(np.float32) \n    \n    mae_raw = np.mean(np.abs(y_eval_f32 - y_pred_raw))\n    mae_constrained = np.mean(np.abs(y_eval_f32 - y_pred_constrained))\n    \n    print(f\"  Mean Absolute Error (Raw Prediction):       {mae_raw:.4f}\")\n    print(f\"  Mean Absolute Error (Constrained Prediction): {mae_constrained:.4f}\")\n    \n    # Calculate SSIM (using constrained predictions as the final output)\n    ssim_scores = []\n    # Determine data range for SSIM (e.g., min/max velocity in ground truth)\n    data_range = np.max(y_eval_f32) - np.min(y_eval_f32)\n    if data_range == 0: data_range = 1.0 # Avoid division by zero if data is constant\n    \n    for i in range(eval_samples):\n        score = ssim(y_eval_f32[i], y_pred_constrained[i], data_range=data_range)\n        ssim_scores.append(score)\n    avg_ssim = np.mean(ssim_scores)\n    print(f\"  Average Structural Similarity Index (SSIM) (Constrained): {avg_ssim:.4f}\")\n\n    # --- Visualize Results for Selected Samples ---\n    print(f\"\\nVisualizing results for {num_samples_to_plot} random samples...\")\n    if eval_samples < num_samples_to_plot:\n         print(f\"  (Requested {num_samples_to_plot} samples, but only {eval_samples} available)\")\n         num_samples_to_plot = eval_samples\n         \n    indices = np.random.choice(eval_samples, num_samples_to_plot, replace=False)\n    \n    for i, idx in enumerate(indices):\n        print(f\"\\n--- Sample {i+1} (Index {idx}) ---\")\n        fig, axes = plt.subplots(1, 3, figsize=(22, 6))\n        \n        vmin = np.min(y_eval_f32[idx])\n        vmax = np.max(y_eval_f32[idx])\n        \n        # Ground Truth\n        im0 = axes[0].imshow(y_eval_f32[idx], cmap='viridis', vmin=vmin, vmax=vmax)\n        axes[0].set_title(f'Ground Truth (Index {idx})')\n        axes[0].set_xlabel('X Position')\n        axes[0].set_ylabel('Y Position')\n        plt.colorbar(im0, ax=axes[0], label='Velocity (m/s)')\n        \n        # Raw Model Prediction\n        im1 = axes[1].imshow(y_pred_raw[idx], cmap='viridis', vmin=vmin, vmax=vmax)\n        axes[1].set_title('Raw Model Prediction')\n        axes[1].set_xlabel('X Position'); axes[1].set_ylabel('Y Position')\n        plt.colorbar(im1, ax=axes[1], label='Velocity (m/s)')\n        \n        # Physics-Constrained Prediction\n        im2 = axes[2].imshow(y_pred_constrained[idx], cmap='viridis', vmin=vmin, vmax=vmax)\n        axes[2].set_title('Physics-Constrained Prediction')\n        axes[2].set_xlabel('X Position'); axes[2].set_ylabel('Y Position')\n        plt.colorbar(im2, ax=axes[2], label='Velocity (m/s)')\n        \n        plt.tight_layout(rect=[0, 0.03, 1, 0.95]) # Adjust layout to prevent title overlap\n        plt.suptitle(f\"Velocity Model Comparison - Sample {idx}\", fontsize=16)\n        plt.show()\n        \n        # Error Maps\n        fig_err, axes_err = plt.subplots(1, 2, figsize=(16, 6))\n        \n        error_raw = np.abs(y_eval_f32[idx] - y_pred_raw[idx])\n        error_constrained = np.abs(y_eval_f32[idx] - y_pred_constrained[idx])\n        err_max = np.max([np.max(error_raw), np.max(error_constrained)]) # Consistent color scale\n        \n        im_err1 = axes_err[0].imshow(error_raw, cmap='hot', vmin=0, vmax=err_max)\n        axes_err[0].set_title('Absolute Error Map (Raw Prediction)')\n        axes_err[0].set_xlabel('X Position'); axes_err[0].set_ylabel('Y Position')\n        plt.colorbar(im_err1, ax=axes_err[0], label='Velocity Error (m/s)')\n        \n        im_err2 = axes_err[1].imshow(error_constrained, cmap='hot', vmin=0, vmax=err_max)\n        axes_err[1].set_title('Absolute Error Map (Constrained Prediction)')\n        axes_err[1].set_xlabel('X Position'); axes_err[1].set_ylabel('Y Position')\n        plt.colorbar(im_err2, ax=axes_err[1], label='Velocity Error (m/s)')\n        \n        plt.tight_layout(rect=[0, 0.03, 1, 0.95])\n        plt.suptitle(f\"Prediction Error Comparison - Sample {idx}\", fontsize=16)\n        plt.show()\n        \n    print(\"--- Model Evaluation Finished ---\")","metadata":{"_cell_guid":"130c9ea7-e878-4db5-ae23-364148732150","_uuid":"9fe42420-c13e-4715-a90c-507098d33878","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:40.322354Z","iopub.execute_input":"2025-05-08T07:25:40.322626Z","iopub.status.idle":"2025-05-08T07:25:40.336460Z","shell.execute_reply.started":"2025-05-08T07:25:40.322604Z","shell.execute_reply":"2025-05-08T07:25:40.335740Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.021082,"end_time":"2025-04-11T19:18:28.447605","exception":false,"start_time":"2025-04-11T19:18:28.426523","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"927bb140","cell_type":"code","source":"# CODE CELL 10: Submission Preparation Function\ndef prepare_submission(model, test_data, oids, input_shape, \n                       batch_size_pred=16, submission_file='submission.csv'):\n    \"\"\"\n    Generates predictions for test data and prepares the Kaggle submission file.\n    \n    Args:\n        model: Trained Keras model.\n        test_data: Dictionary mapping oid to test seismic data arrays.\n        oids: List of object IDs (keys in test_data) in desired order.\n        input_shape: Model's expected input shape (T, R, S).\n        batch_size_pred: Batch size for prediction on test data.\n        submission_file: Name of the output CSV file.\n        \n    Returns:\n        submission_df: Pandas DataFrame containing the submission.\n    \"\"\"\n    print(\"\\n--- Preparing Kaggle Submission ---\")\n    if not test_data:\n        print(\"Test data dictionary is empty. Cannot generate submission.\")\n        return pd.DataFrame()\n        \n    all_predictions_list = []\n    \n    print(f\"Generating predictions for {len(oids)} test samples...\")\n    for oid in tqdm(oids, desc=\"Processing test samples\"):\n        data_raw = test_data[oid] # Shape (batch=1, S, T, R)\n        \n        # Preprocess (normalize)\n        # Note: preprocess_seismic_batch expects (batch, S, T, R)\n        data_preprocessed = preprocess_seismic_batch(data_raw)\n        \n        # Reshape for model input (batch, T, R, S)\n        try:\n            data_reshaped = np.transpose(data_preprocessed, (0, 2, 3, 1))\n            if data_reshaped.shape[1:] != input_shape:\n                 raise ValueError(f\"Test data oid {oid} shape {data_reshaped.shape[1:]} != expected input {input_shape}\")\n        except Exception as e:\n             print(f\"Error reshaping test data for oid {oid}: {e}. Skipping this sample.\")\n             continue # Skip this sample if reshaping fails\n\n        # Predict (model expects batch dimension)\n        predictions_raw = model.predict(data_reshaped, batch_size=batch_size_pred, verbose=0)\n        \n        # Apply physics constraints\n        predictions_constrained = apply_output_constraints(predictions_raw, min_velocity=1500.0, smoothing_sigma=0.5)\n        \n        # Extract the single predicted map (output shape is likely (1, H, W))\n        if predictions_constrained.shape[0] != 1:\n            print(f\"Warning: Unexpected batch dimension in prediction for oid {oid}. Shape: {predictions_constrained.shape}. Using first element.\")\n        vel_map = predictions_constrained[0] # Shape (H, W)\n        height, width = vel_map.shape\n        \n        # Extract required values for submission format\n        for y_pos in range(height):\n            # Get values at odd horizontal indices (x=1, 3, 5, ...)\n            odd_indices = np.arange(1, width, 2) \n            if len(odd_indices) == 0: continue # Skip if width is 0 or 1\n\n            values = vel_map[y_pos, odd_indices]\n            \n            row_id = f\"{oid}_y_{y_pos}\"\n            row_dict = {\"oid_ypos\": row_id}\n            \n            # Populate the dictionary with x_i columns\n            for i, val in enumerate(values):\n                col_name = f\"x_{2*i + 1}\" # x_1, x_3, x_5, ...\n                row_dict[col_name] = val\n            \n            all_predictions_list.append(row_dict)\n            \n    # Create DataFrame\n    submission_df = pd.DataFrame(all_predictions_list)\n    \n    # Ensure columns are in the expected order (oid_ypos, x_1, x_3, ...)\n    if not submission_df.empty:\n        first_row_keys = list(all_predictions_list[0].keys())\n        # Find max x index from column names like 'x_i'\n        x_cols = [col for col in first_row_keys if col.startswith('x_')]\n        if x_cols:\n             max_x_index = max([int(col.split('_')[1]) for col in x_cols])\n             expected_x_cols = [f\"x_{i}\" for i in range(1, max_x_index + 1, 2)]\n             column_order = [\"oid_ypos\"] + expected_x_cols\n             # Reorder df columns, handling potential missing columns if width varies?\n             submission_df = submission_df.reindex(columns=column_order) \n        else:\n             column_order = [\"oid_ypos\"] # Case where no x columns were generated\n             submission_df = submission_df.reindex(columns=column_order)\n\n    # Save to CSV\n    try:\n        submission_df.to_csv(submission_file, index=False)\n        print(f\"Submission file saved to '{submission_file}' with {len(submission_df)} rows and {len(submission_df.columns)} columns.\")\n        print(\"\\nSubmission Sample (first 5 rows):\")\n        print(submission_df.head())\n    except Exception as e:\n        print(f\"Error saving submission file: {e}\")\n\n    print(\"--- Submission Preparation Finished ---\")\n    return submission_df","metadata":{"_cell_guid":"af57abb7-d49b-446a-a882-dcfe8754e2ba","_uuid":"d3dcbf4d-148f-47c6-95cd-680f5a23f07e","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:47.229430Z","iopub.execute_input":"2025-05-08T07:25:47.229665Z","iopub.status.idle":"2025-05-08T07:25:47.239945Z","shell.execute_reply.started":"2025-05-08T07:25:47.229649Z","shell.execute_reply":"2025-05-08T07:25:47.239360Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":0.017082,"end_time":"2025-04-11T19:18:28.481261","exception":false,"start_time":"2025-04-11T19:18:28.464179","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"e698600a","cell_type":"code","source":"# CODE CELL 11: Main Execution Function\ndef main():\n    \"\"\"Main function to orchestrate the FWI pipeline.\"\"\"\n    print(\"==============================================================\")\n    print(\" Starting Yale/UNC-CH Geophysical Waveform Inversion Pipeline \")\n    print(\"==============================================================\")\n    print(f\"Timestamp: {pd.Timestamp.now()}\")\n    \n    # --- Configuration ---\n    # Set a sample limit for faster testing/debugging, None to use all data\n    TRAINING_SAMPLE_LIMIT = None # e.g., 100 or 500. Set to None for full run.\n    EPOCHS = 30 # Adjust as needed\n    BATCH_SIZE = 8 # Adjust based on GPU memory\n    MODEL_SAVE_NAME = 'best_physics_guided_unet.h5' # Specific name for the saved model\n    SUBMISSION_FILENAME = 'submission.csv'\n    \n    # --- 1. Load Training Data ---\n    print(\"\\n=== 1. Loading Training Data ===\")\n    train_data = {}\n    if not os.path.exists(TRAIN_DIR):\n        print(f\"ERROR: Training directory '{TRAIN_DIR}' not found. Exiting.\")\n        return\n        \n    available_families = [d for d in os.listdir(TRAIN_DIR) if os.path.isdir(os.path.join(TRAIN_DIR, d))]\n    if not available_families:\n        print(f\"ERROR: No dataset families found in '{TRAIN_DIR}'. Exiting.\")\n        return\n        \n    print(f\"Found families: {available_families}\")\n    \n    X_all_list, y_all_list = [], []\n    for family in available_families:\n        print(f\"\\n--- Processing Family: {family} ---\")\n        X, y = load_training_data(family, sample_limit=TRAINING_SAMPLE_LIMIT)\n        if X is not None and y is not None:\n            print(f\"Loaded {family}: X shape={X.shape}, y shape={y.shape}\")\n            X_all_list.append(X)\n            y_all_list.append(y)\n            # Optional: Visualize one sample per family\n            # plot_seismic_and_velocity(X, y, sample_idx=0, title_prefix=f\"{family} - \")\n        else:\n            print(f\"Skipping family {family} due to loading issues.\")\n        gc.collect() # Clean up memory after loading each family\n\n    if not X_all_list:\n        print(\"ERROR: No training data could be loaded. Exiting.\")\n        return\n\n    X_all = np.concatenate(X_all_list, axis=0)\n    y_all = np.concatenate(y_all_list, axis=0)\n    del X_all_list, y_all_list # Free memory\n    gc.collect()\n    print(f\"\\nCombined Training Data: X shape={X_all.shape}, y shape={y_all.shape}\")\n    \n    # --- 2. Data Exploration (Combined Data) ---\n    print(\"\\n=== 2. Data Visualization (Combined Sample) ===\")\n    num_samples_to_plot = min(2, len(X_all))\n    if num_samples_to_plot > 0:\n         for i in range(num_samples_to_plot):\n             plot_seismic_and_velocity(X_all, y_all, sample_idx=i, title_prefix=\"Combined \")\n    else:\n         print(\"No combined data available to plot.\")\n\n    # --- 3. Data Splitting ---\n    print(\"\\n=== 3. Splitting Data into Training/Validation ===\")\n    if len(X_all) < 2:\n         print(\"ERROR: Not enough data to split into training and validation sets. Need at least 2 samples.\")\n         # Decide how to handle: exit, or use all data for training (no validation)?\n         # For now, we'll exit if we can't validate.\n         return \n         \n    val_size = 0.2 # 20% for validation\n    try:\n        X_train, X_val, y_train, y_val = train_test_split(X_all, y_all, test_size=val_size, random_state=42)\n        del X_all, y_all # Free memory\n        gc.collect()\n        print(f\"Training set:   X shape={X_train.shape}, y shape={y_train.shape}\")\n        print(f\"Validation set: X shape={X_val.shape}, y shape={y_val.shape}\")\n    except Exception as e:\n        print(f\"Error during train/test split: {e}\")\n        return\n\n    # --- 4. Determine Shapes ---\n    print(\"\\n=== 4. Determining Model Input/Output Shapes ===\")\n    try:\n        # X shape: (batch, S, T, R) -> Model Input (T, R, S)\n        _, num_sources, time_steps, num_receivers = X_train.shape \n        # y shape: (batch, H, W) -> Model Output (H, W)\n        _, height, width = y_train.shape \n        \n        input_shape = (time_steps, num_receivers, num_sources)\n        output_shape = (height, width)\n        print(f\"Deduced Input Shape (T, R, S): {input_shape}\")\n        print(f\"Deduced Output Shape (H, W): {output_shape}\")\n    except Exception as e:\n        print(f\"Error determining shapes from training data: {e}\")\n        return\n\n    # --- 5. Build Model ---\n    print(\"\\n=== 5. Building Neural Network Model ===\")\n    # Choose which model to build here\n    # model = build_resnet_style_model(input_shape, output_shape)\n    model = build_physics_guided_unet(input_shape, output_shape, \n                                      base_filters=64, depth=4, # Example parameters\n                                      smoothness_weight=0.05, min_velocity=1500.0)\n    model.summary(line_length=120)\n    \n    # --- 6. Train Model ---\n    print(\"\\n=== 6. Training Model ===\")\n    trained_model, history = train_model(model, X_train, y_train, X_val, y_val, \n                                         input_shape=input_shape, output_shape=output_shape,\n                                         batch_size=BATCH_SIZE, epochs=EPOCHS, \n                                         model_save_path=MODEL_SAVE_NAME)\n    \n    # --- 7. Plot Training History ---\n    print(\"\\n=== 7. Plotting Training History ===\")\n    if history and history.history:\n        try:\n            plt.figure(figsize=(14, 6))\n            \n            # Loss Plot\n            plt.subplot(1, 2, 1)\n            if 'loss' in history.history: plt.plot(history.history['loss'], label='Training Loss')\n            if 'val_loss' in history.history: plt.plot(history.history['val_loss'], label='Validation Loss')\n            plt.title('Model Loss')\n            plt.ylabel('Loss Value')\n            plt.xlabel('Epoch')\n            plt.legend(loc='upper right')\n            plt.grid(True, linestyle='--', alpha=0.6)\n            \n            # MAE Plot (or other primary metric)\n            plt.subplot(1, 2, 2)\n            primary_metric = 'mae' # Or 'mean_absolute_error' depending on tf version/naming\n            val_primary_metric = f'val_{primary_metric}'\n            if primary_metric in history.history: plt.plot(history.history[primary_metric], label=f'Training {primary_metric.upper()}')\n            if val_primary_metric in history.history: plt.plot(history.history[val_primary_metric], label=f'Validation {primary_metric.upper()}')\n            plt.title('Model Mean Absolute Error (MAE)')\n            plt.ylabel('MAE Value')\n            plt.xlabel('Epoch')\n            plt.legend(loc='upper right')\n            plt.grid(True, linestyle='--', alpha=0.6)\n            \n            plt.tight_layout()\n            plt.show()\n        except Exception as e:\n            print(f\"Could not plot training history: {e}\")\n    else:\n        print(\"No training history available to plot.\")\n\n    # --- 8. Evaluate Model ---\n    print(\"\\n=== 8. Evaluating Model on Validation Set ===\")\n    evaluate_model(trained_model, X_val, y_val, input_shape=input_shape, \n                   num_samples_to_plot=min(3, len(X_val)), # Plot up to 3 samples\n                   batch_size_eval=BATCH_SIZE) # Use same batch size or adjust for memory\n                   \n    # Optional: Clean up validation data if no longer needed\n    # del X_val, y_val \n    # gc.collect()\n\n    # --- 9. Load Test Data ---\n    print(\"\\n=== 9. Loading Test Data ===\")\n    if not os.path.exists(TEST_DIR):\n         print(f\"Warning: Test directory '{TEST_DIR}' not found. Skipping submission generation.\")\n         test_data, oids = {}, []\n    else:\n         test_data, oids = load_test_data()\n         if test_data:\n             print(f\"Loaded {len(oids)} test samples. Example OID: {oids[0] if oids else 'N/A'}\")\n             # print(f\"  Example test data shape: {test_data[oids[0]].shape if oids else 'N/A'}\")\n         else:\n             print(\"No test data loaded.\")\n\n    # --- 10. Prepare Submission ---\n    print(\"\\n=== 10. Preparing Submission File ===\")\n    if test_data and oids:\n        submission_df = prepare_submission(trained_model, test_data, oids, \n                                           input_shape=input_shape, \n                                           batch_size_pred=BATCH_SIZE, # Adjust if needed for test inference\n                                           submission_file=SUBMISSION_FILENAME)\n        # submission_df now holds the result, already saved to CSV\n    else:\n        print(\"Skipping submission file generation as no test data was loaded.\")\n\n    print(\"\\n==============================================================\")\n    print(\" Pipeline Execution Finished\")\n    print(\"==============================================================\")\n\n# --- Entry Point Check ---\nif __name__ == \"__main__\":\n    # This ensures the main function runs only when the script is executed directly\n    # (not when imported as a module)\n    main()\n    # Optional: Explicitly clear session if running multiple times in one environment\n    # tf.keras.backend.clear_session() \n    # gc.collect()","metadata":{"_cell_guid":"62438002-aba5-4306-83c8-845b9fbe8b84","_uuid":"125299bd-f6e1-48df-9701-c8e5b6f725d6","collapsed":false,"execution":{"iopub.status.busy":"2025-05-08T07:25:53.847824Z","iopub.execute_input":"2025-05-08T07:25:53.848104Z"},"jupyter":{"outputs_hidden":false},"papermill":{"duration":46.981019,"end_time":"2025-04-11T19:19:15.478785","exception":false,"start_time":"2025-04-11T19:18:28.497766","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null}]}