diff --git a/docs/PopSyCLE_example.ipynb b/docs/PopSyCLE_example.ipynb index de01208..b6994ef 100755 --- a/docs/PopSyCLE_example.ipynb +++ b/docs/PopSyCLE_example.ipynb @@ -419,7 +419,7 @@ "source": [ "# Photometry\n", "\n", - "This is the last thing, where you choose the photometric band for the observations and the reddening law.\n", + "This is the last thing, where you choose the photometric band or bands for the observations and the reddening law.\n", "The final file of microlensing events is produced here as a .fits file." ] }, @@ -442,8 +442,7 @@ ], "source": [ "synthetic.refine_events(input_root = 'example', \n", - " filter_name = 'I',\n", - " photometric_system = 'ubv',\n", + " filter_dict={'ubv':['I']},\n", " red_law = 'Damineli16', \n", " overwrite = False, \n", " output_file = 'default')" diff --git a/docs/PopSyCLE_example_multiples.ipynb b/docs/PopSyCLE_example_multiples.ipynb index 0a7ceb4..e49166a 100755 --- a/docs/PopSyCLE_example_multiples.ipynb +++ b/docs/PopSyCLE_example_multiples.ipynb @@ -10,7 +10,7 @@ "source": [ "import warnings\n", "warnings.filterwarnings('ignore')\n", - "from popsycle import synthetic, utils, binary_utils\n", + "from popsycle import synthetic, utils, binary_utils, table_utils\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from astropy.table import Table\n", @@ -831,7 +831,7 @@ "source": [ "# Photometry\n", "\n", - "In refine_events you specify a photometric band for the observations and the reddening law.\n", + "In refine_events you specify a photometric band or bands for the observations and the reddening law.\n", "The final file of microlensing events is produced here as a .fits file.\n", "To include multiples you must specify the hdf5 companion file." ] @@ -860,8 +860,7 @@ "source": [ "synthetic.refine_events(input_root = 'example',\n", " hdf5_file_comp = 'example_companions.h5',\n", - " filter_name = 'I',\n", - " photometric_system = 'ubv',\n", + " filter_dict={'ubv':['I']},\n", " red_law = 'Damineli16', \n", " overwrite = False, \n", " output_file = 'default')" @@ -1172,14 +1171,14 @@ "metadata": {}, "outputs": [], "source": [ - "t_cut, t_cut_one_peak, t_multiples_cut_multi_peak = binary_utils.cut_Mruns(t, \n", - " t_companions, \n", - " t_peaks, \n", - " min_mag = 22, \n", - " delta_m_cut = 0.1, \n", - " u0_cut = 2, \n", - " ubv_filter = 'I', \n", - " S_LSN = 'S')" + "t_cut, t_cut_one_peak, t_multiples_cut_multi_peak = table_utils.cut_Mruns(t, \n", + " t_companions, \n", + " t_peaks, \n", + " min_mag = 22, \n", + " delta_m_cut = 0.1, \n", + " u0_cut = 2, \n", + " ubv_filter = 'I', \n", + " S_LSN = 'S')" ] }, { diff --git a/docs/PopSyCLE_example_run.ipynb b/docs/PopSyCLE_example_run.ipynb index 5c7ea4d..81cccd9 100755 --- a/docs/PopSyCLE_example_run.ipynb +++ b/docs/PopSyCLE_example_run.ipynb @@ -43,8 +43,7 @@ " bin_edges_number=None,\n", " BH_kick_speed_mean=50,\n", " NS_kick_speed_mean=400,\n", - " photometric_system='ubv',\n", - " filter_name='R',\n", + " filter_dict={'ubv':['R']},\n", " red_law='Damineli16',\n", " multiplicity=None,\n", " galaxia_galaxy_model_filename= '/g/lu/code/galaxia/docs/galaxyModelParams_PopSyCLEv3.txt')" diff --git a/docs/PopSyCLE_example_slurm.ipynb b/docs/PopSyCLE_example_slurm.ipynb index 3d8b932..68f4d33 100755 --- a/docs/PopSyCLE_example_slurm.ipynb +++ b/docs/PopSyCLE_example_slurm.ipynb @@ -50,7 +50,7 @@ " n_cores_per_node=64,\n", " n_nodes_max=3072,\n", " walltime_max=24,\n", - " additional_lines=['module load cray-hdf5/1.10.5.2', 'export HDF5_USE_FILE_LOCKING=FALSE'])" + " additional_lines=['module load cray-hdf5-parallel', 'export HDF5_USE_FILE_LOCKING=FALSE'])" ] }, { @@ -78,8 +78,7 @@ " bin_edges_number=None,\n", " BH_kick_speed_mean=50,\n", " NS_kick_speed_mean=400,\n", - " photometric_system='ubv',\n", - " filter_name='r',\n", + " filter_dict={'ubv':['R']},\n", " red_law='Damineli16',\n", " multiplicity=None)" ] diff --git a/docs/PopSyCLE_example_synthpop.ipynb b/docs/PopSyCLE_example_synthpop.ipynb new file mode 100755 index 0000000..a982eef --- /dev/null +++ b/docs/PopSyCLE_example_synthpop.ipynb @@ -0,0 +1,874 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "pycharm": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "import warnings\n", + "warnings.filterwarnings('ignore')\n", + "from popsycle import synthetic\n", + "from popsycle import utils\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from astropy.table import Table\n", + "import synthpop\n", + "import pdb\n", + "import time" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": {} + }, + "source": [ + "# SynthPop\n", + "\n", + "This is where you generate the stellar model. Start by initializing SynthPop object with the configuration of your choice.\n", + "This may take a few minutes if it needs to download isochrones and/or several seconds to load a large extinction map file.\n", + "\n", + "For PopSyCLE, it is required to use the popsycle_post_processing module to get your data in the right format. Additionally, you must use absolute magnitudes and use filters that PopSyCLE can interpret." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "pycharm": { + "metadata": false, + "name": "#%%\n" + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 10383 - Execution Date: 09-09-2025 14:27:41\n", + "\n", + "\n", + "################################ Settings #################################\n", + " 10386 - # reading default parameters from\n", + " 10388 - default_config_file = /u/mhuston/code/synthpop/synthpop/config_files/_default.synthpop_conf \n", + " 10393 - # read configuration from \n", + " 10395 - config_file = '/u/mhuston/code/synthpop/synthpop/config_files/huston2025_defaults.synthpop_conf' \n", + "\n", + "\n", + "# copy the following to a config file to redo this model generation -------\n", + " 10398 - {\n", + " \"l_set\": null,\n", + " \"l_set_type\": null,\n", + " \"b_set\": null,\n", + " \"b_set_type\": null,\n", + " \"name_for_output\": \"synthpopsycle\",\n", + " \"model_name\": \"Huston2025\",\n", + " \"solid_angle\": null,\n", + " \"solid_angle_unit\": \"deg^2\",\n", + " \"random_seed\": 1993927877,\n", + " \"sun\": {\n", + " \"x\": -8.178,\n", + " \"y\": 0.0,\n", + " \"z\": 0.017,\n", + " \"u\": 12.9,\n", + " \"v\": 245.6,\n", + " \"w\": 7.78,\n", + " \"l_apex_deg\": 56.24,\n", + " \"b_apex_deg\": 22.54\n", + " },\n", + " \"lsr\": {\n", + " \"u_lsr\": 1.8,\n", + " \"v_lsr\": 233.4,\n", + " \"w_lsr\": 0.53\n", + " },\n", + " \"warp\": {\n", + " \"r_warp\": 7.72,\n", + " \"amp_warp\": 0.06,\n", + " \"amp_warp_pos\": null,\n", + " \"amp_warp_neg\": null,\n", + " \"alpha_warp\": 1.33,\n", + " \"phi_warp_deg\": 17.5\n", + " },\n", + " \"max_distance\": 25,\n", + " \"distance_step_size\": 0.1,\n", + " \"window_type\": {\n", + " \"window_type\": \"cone\"\n", + " },\n", + " \"mass_lims\": {\n", + " \"min_mass\": 0.08,\n", + " \"max_mass\": 100\n", + " },\n", + " \"N_mc_totmass\": 10000,\n", + " \"lost_mass_option\": 1,\n", + " \"N_av_mass\": 50000,\n", + " \"kinematics_at_the_end\": false,\n", + " \"scale_factor\": 1,\n", + " \"skip_lowmass_stars\": false,\n", + " \"chunk_size\": 250000,\n", + " \"extinction_map_kwargs\": {\n", + " \"name\": \"maps_from_dustmaps\",\n", + " \"dustmap_name\": \"marshall\"\n", + " },\n", + " \"extinction_law_kwargs\": [\n", + " {\n", + " \"name\": \"SODC\",\n", + " \"R_V\": 2.5\n", + " }\n", + " ],\n", + " \"evolution_class\": {\n", + " \"name\": \"MIST\",\n", + " \"interpolator\": \"CharonInterpolator\"\n", + " },\n", + " \"maglim\": [\n", + " \"Bessell_U\",\n", + " 99,\n", + " \"keep\"\n", + " ],\n", + " \"chosen_bands\": [\n", + " \"Bessell_U\",\n", + " \"Bessell_B\",\n", + " \"Bessell_V\",\n", + " \"Bessell_R\",\n", + " \"Bessell_I\",\n", + " \"2MASS_J\",\n", + " \"2MASS_H\",\n", + " \"2MASS_Ks\"\n", + " ],\n", + " \"eff_wavelengths\": {\n", + " \"Bessell_U\": 0.365988,\n", + " \"Bessell_B\": 0.438074,\n", + " \"Bessell_V\": 0.544543,\n", + " \"Bessell_R\": 0.641147,\n", + " \"Bessell_I\": 0.798209,\n", + " \"2MASS_J\": 1.228538,\n", + " \"2MASS_H\": 1.63861,\n", + " \"2MASS_Ks\": 2.152164\n", + " },\n", + " \"obsmag\": false,\n", + " \"opt_iso_props\": [\n", + " \"log_L\",\n", + " \"log_Teff\",\n", + " \"log_g\",\n", + " \"[Fe/H]\",\n", + " \"log_R\",\n", + " \"phase\"\n", + " ],\n", + " \"col_names\": [\n", + " \"logL\",\n", + " \"logTeff\",\n", + " \"logg\",\n", + " \"Fe/H_evolved\",\n", + " \"log_radius\",\n", + " \"phase\"\n", + " ],\n", + " \"post_processing_kwargs\": [\n", + " {\n", + " \"name\": \"ProcessDarkCompactObjects\",\n", + " \"remove\": false,\n", + " \"ifmr_name\": \"SukhboldN20\",\n", + " \"kick_mean_ns\": 350,\n", + " \"kick_mean_bh\": 100\n", + " },\n", + " {\n", + " \"name\": \"popsycle_post_processing\"\n", + " }\n", + " ],\n", + " \"output_location\": \"synthpop_output\",\n", + " \"output_filename_pattern\": \"{name_for_output}_l{l_deg:.3f}_b{b_deg:.3f}\",\n", + " \"output_file_type\": \"csv\",\n", + " \"overwrite\": true\n", + "}\n", + " 10400 - Location or solid_angle_sr are not defined in the settings! Can not run main() or process_all()\n", + "\n", + "\n", + "########################## initialize population ##########################\n", + " 10419 - read Population files from Huston2025\n", + "\n", + "\n", + "# Population 0; bulge ----------------------------------------------------\n", + " 10434 - # Initialize Population 0 (bulge) from \n", + " 10435 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/bulge.popjson'\n", + "\n", + "\n", + "# Population 1; halo -----------------------------------------------------\n", + " 22311 - # Initialize Population 1 (halo) from \n", + " 22314 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/halo.popjson'\n", + "\n", + "\n", + "# Population 2; nsd ------------------------------------------------------\n", + " 23712 - # Initialize Population 2 (nsd) from \n", + " 23713 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/nsd.popjson'\n", + "\n", + "\n", + "# Population 3; thick_disk -----------------------------------------------\n", + " 25857 - # Initialize Population 3 (thick_disk) from \n", + " 25859 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thick_disk.popjson'\n", + "\n", + "\n", + "# Population 4; thin_disk_1 ----------------------------------------------\n", + " 27188 - # Initialize Population 4 (thin_disk_1) from \n", + " 27190 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thin_disk_1.popjson'\n", + "\n", + "\n", + "# Population 5; thin_disk_2 ----------------------------------------------\n", + " 28478 - # Initialize Population 5 (thin_disk_2) from \n", + " 28479 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thin_disk_2.popjson'\n", + "\n", + "\n", + "# Population 6; thin_disk_3 ----------------------------------------------\n", + " 29857 - # Initialize Population 6 (thin_disk_3) from \n", + " 29858 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thin_disk_3.popjson'\n", + "\n", + "\n", + "# Population 7; thin_disk_4 ----------------------------------------------\n", + " 31175 - # Initialize Population 7 (thin_disk_4) from \n", + " 31177 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thin_disk_4.popjson'\n", + "\n", + "\n", + "# Population 8; thin_disk_5 ----------------------------------------------\n", + " 32540 - # Initialize Population 8 (thin_disk_5) from \n", + " 32542 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thin_disk_5.popjson'\n", + "\n", + "\n", + "# Population 9; thin_disk_6 ----------------------------------------------\n", + " 33892 - # Initialize Population 9 (thin_disk_6) from \n", + " 33894 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thin_disk_6.popjson'\n", + "\n", + "\n", + "# Population 10; thin_disk_7 ---------------------------------------------\n", + " 35195 - # Initialize Population 10 (thin_disk_7) from \n", + " 35197 - pop_file = '/u/mhuston/code/synthpop/synthpop/models/Huston2025/thin_disk_7.popjson'\n", + " 36546 - # all population are initialized\n" + ] + } + ], + "source": [ + "sp_mod = synthpop.SynthPop('huston2025_defaults.synthpop_conf', # set default figuration, then set any overrides as kwargs\n", + " output_location=\"synthpop_output\", # where to place the synthpop output\n", + " obsmag = False, # keep magnitudes in absolute\n", + " name_for_output='synthpopsycle', output_filename_pattern=\"{name_for_output}_l{l_deg:.3f}_b{b_deg:.3f}\", #how to name the output\n", + " post_processing_kwargs = [{\"name\":\"ProcessDarkCompactObjects\", \"remove\":False, \"ifmr_name\":'SukhboldN20',\n", + " \"kick_mean_ns\":350, \"kick_mean_bh\":100}, # IFMR + kick velocities\n", + " {\"name\": \"popsycle_post_processing\"}], # Module to format catalog for PopSyCLE\n", + " chosen_bands=[\"Bessell_U\", \"Bessell_B\", \"Bessell_V\", \"Bessell_R\", \"Bessell_I\", \"2MASS_J\", \"2MASS_H\", \"2MASS_Ks\"],\n", + " maglim=['Bessell_U',99,'keep'],\n", + " extinction_map_kwargs = {\"name\":\"maps_from_dustmaps\", \"dustmap_name\":\"marshall\"} #{\"name\":\"surot\"}\n", + " )\n", + "sp_mod.init_populations()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "metadata": false + } + }, + "source": [ + "Then, run the specific sightline you want" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "pycharm": { + "metadata": false, + "name": "#%%\n" + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "\n", + "############################# update location #############################\n", + " 36620 - # set location to: \n", + " 36622 - l, b = (1.00 deg, -1.20 deg)\n", + " 36624 - # set solid_angle to:\n", + " 36626 - solid_angle = 1.000e-03 deg^2\n", + "\n", + "\n", + "############################# Generate Field ##############################\n", + "\n", + "\n", + "# Population 0; bulge ----------------------------------------------------\n", + " 49343 - # From density profile (number density)\n", + " 49346 - expected_total_iMass = 404149.1758\n", + " 49348 - expected_total_eMass = 220493.1658\n", + " 49350 - average_iMass_per_star = 0.5739\n", + " 49352 - mass_loss_correction = 0.5456\n", + " 49354 - n_expected_stars = 704257.1067\n", + " 49360 - # Determine velocities when position are generated \n" + ] + }, + { + "data": { + "application/json": { + "ascii": false, + "bar_format": null, + "colour": null, + "elapsed": 0.013125181198120117, + "initial": 0, + "n": 0, + "ncols": null, + "nrows": 65, + "postfix": null, + "prefix": "", + "rate": null, + "total": 704252, + "unit": "it", + "unit_divisor": 1000, + "unit_scale": false + }, + "application/vnd.jupyter.widget-view+json": { + "model_id": "ca10193b34ed4d0cb99b05866aca0c31", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/704252 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.hist(t['t_E'], bins = np.logspace(0, 2.5, 15))\n", + "plt.xscale('log')\n", + "plt.yscale('log')\n", + "plt.xlabel('$t_E$ (days)')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": {} + }, + "source": [ + "## piE vs tE" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "pycharm": {} + }, + "outputs": [ + { + "data": { + "image/png": "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+PHHHwEAI0aMcPCM/qLRaPDoo4/il19+QWhoKDZs2IAGDRo4tK8VK1bIc8B07doV//d//1elur399ttyPsUnn3yC/v37my0bEhICQD+lhTNnaP7111/l35WUQOztcnJyMHLkSGg0GoSHh+Obb74xyscxpXxuY/m/mzMwwPEDwcHB+PDDD7F37160b99efvzy5csYNmwYpk+fLk/MZlCjRg35izgjI6PS9OYrV67ErVu3IEkSJk6caPbY999/v9k5JcLDw+XfLSWi2WP+/Pnyl6ajx0pOToYkSQgPD0fHjh3x3nvvoaioCGFhYUhOTsZbb71lVH779u0A9EFM3759ze7XkEQJ6JN1zQkLC0NYWJjZ5zt37iz/fvz48UrPS5KExMREpKWlGSVX/vnnn5g4cSJGjx5dKUlarVZj3LhxAPTTr69fv97o+ZSUFPkC9Oyzzxo9V/69o1Ip8yslOjoa99xzD4DKAY4hoXvo0KHo168fAH3S8cWLF+UyW7ZsQUlJCWrUqIEHHnigyvWZOnUqtm7dCpVKhX/9618WE5utMbx/AcjvAUetXr0as2bNAgC89NJLmDRpksXyDz74IAB9wNa9e3esXr1avlmoivITl1r6LJHzXLlyBX379sWZM2dQvXp1bN68GXfddZfV7cr/fZw94awyv43IpK5du+LYsWP44osvEBUVJT++YMECJCYmVir/zDPPyL8vWbLE6LnPP/8cgH5USLNmzcwe09J6JK7gzOOFhISgfv36GDhwIBYuXIhz586ZnNzvzJkzAPSz9hpavcwxvFbnzp1zOKgrf0d69epVs+Xi4uKwY8cOrFu3zmjk2erVq/HUU09VKv/000/LrRQV/96LFy8GALRo0aLSBbpmzZry73/++afN5+FqV69ehdB3w1f6cWQYfUJCAgD9bNGGidM0Gg1SUlIA6AOc3r17y0Fe+Vac//znPwCAIUOGWL2jtebvf/+73Jo2f/58PPTQQ1XaX/mWkw4dOji8n71792LChAkA9Oe5YMECq9s8+eSTGDlyJAD95I+jR49GkyZNkJSUVKWZhcsH8L6y3pm51kZzP97k7Nmz6NatG3755RdER0dj586d6N69u03blv/7mBplVxUMcPyMSqXCxIkTkZmZiYEDB8qP//3vf8fJkyeNysbHx8sX5DVr1qC4uBgAsH//fnkq8Oeee85NNXef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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lens_type={0:'star', 101:'WD', 102:'NS', 103:'BH'}\n", + "for rem_id in np.unique(t['rem_id_L']):\n", + " idxs = np.where(t['rem_id_L']==rem_id)[0]\n", + " plt.scatter(t['t_E'][idxs], t['pi_E'][idxs], label=lens_type[rem_id])\n", + "plt.xscale('log')\n", + "plt.yscale('log')\n", + "plt.xlabel('$t_E$ (days)')\n", + "plt.ylabel('$\\pi_E$')\n", + "plt.legend(loc=2)\n", + "plt.title('SynthPopSyCLE w kicks (1, -1.2)')\n", + "plt.savefig('synthpopsycle_lenstype.png')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": {} + }, + "source": [ + "## dL vs dS" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "pycharm": {} + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAIuCAYAAABZzclzAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACVb0lEQVR4nOzdeVxU5f4H8M8Msq+isoiouIcb4YY74BItLllZ5oKmZtYvtdLUNElb1G55U69p6TVxy2vldUnFNMEktwuGSiQpIm4gCrIIgjDz/P7AGRlmBmZghoGZz/v1ouCc55zznEHmfOdZvo9ECCFAREREZKakpq4AERERkTEx2CEiIiKzxmCHiIiIzBqDHSIiIjJrDHaIiIjIrDHYISIiIrPGYIeIiIjMWgNTV4CqRy6X49atW3B2doZEIjF1dYiIiGqVEAL5+flo2rQppNLK224Y7NRTt27dgq+vr6mrQUREZFLXr19Hs2bNKi3DYKeecnZ2BlD2S3ZxcTFxbYiIiGpXXl4efH19lc/DyjDYqacUXVcuLi4MdoiIyGLpMpSDA5SJiIjIrDHYISIiIrPGYIeIiIjMGoMdIiIiMmsMdoiIiMisMdghIiIis8Zgh4iIiMwagx0iIiIya0wqaKFKSkogk8lMXQ2ies/KygrW1tamrgYRVYLBjoXJy8vD3bt3UVxcbOqqEJkNW1tbNG7cmNnMieooBjsWJC8vDzdv3oSTkxMaN24Ma2trrphOVANCCJSUlCA3Nxc3b94EAAY8RHUQgx0LcvfuXTg5OaFZs2YMcogMxN7eHs7Ozrhx4wbu3r3LYIeoDuIAZQtRUlKC4uJiuLq6MtAhMjCJRAJXV1cUFxejpKTE1NUhogoY7FgIxWBkDqQkMg7F3xYH/hPVPQx2LAxbdR5r2bIlJBIJYmJiTF0VMgP82yJNZHKBkylZ2JNwEydTsiCTC1NXySJxzA4R1Wk5OTn4+OOPsWvXLqSnp8Pb2xujRo3CokWL4OrqaurqEWkVlZiOxfuSkJ5bpNzm7WqHiGH+COvkbcKaWR627BCZgbS0NMTExCAtLc3UVTGo9PR0BAYGYsWKFXB3d8crr7wCd3d3rFixAoGBgUhPTzd1FYk0ikpMx/StZ1UCHQDIyC3C9K1nEZXIf7u1icEOkRkIDw9HSEgIoqOjTV0VgxozZgxSU1OxaNEixMfHY9OmTYiPj0dERASuXLmCV1991dRVJFIjkwss3pcETR1Wim2L9yWxS6sWMdghojrp4MGDOHbsGNq1a4eIiAiVfREREWjfvj1iYmIQFRVlohoSaXYmNVutRac8ASA9twhnUrNrr1IWjsEOGYw5DcQrLS3FqlWrEBAQAHt7e7i7u2PEiBG4cOGCxvISiQQSiQRXr17FlStXEB4eDm9vb7i4uCAkJAQJCQkaj0tOTsa4cePQsmVLZRbeoKAgHD9+XKd6xsTEQCKR4NixYwCASZMmKevy0Ucf4ddff4VEIoGtrS2ysrI0nmPLli2QSCQYM2aMclv5wdvnzp3DqFGj0KRJEzg4OKBbt26IjIzUWqcbN25g6tSp8PHxga2tLVq1aoX58+ejsLBQp3sqXy8AGD16NKRS1bcqiUSC0aNHAwA2b96s13mJjC0zX3ugU51yVHMcoEwGYU4D8UpLS/H888/j559/Rvv27fHiiy/ixo0b2Lt3L44ePYrY2Fh07dpV47HHjx/HW2+9BXt7e4SEhODGjRuIiYlBSEgIkpOT4eHhoSx76dIl9OjRA/fv30dISAhCQ0ORmZmJ+Ph4pKWloX///lXWtWnTppg2bRr27t2L9PR0BAcHo3379gCA7t27IzQ0FK1atcKVK1ewbds2zJgxQ+0c69atAwBMmzZNbd/vv/+OTz/9FD4+Pnj66adx584d/PLLL5g4cSKuX7+OhQsXqpS/cOECBg0ahKysLISGhsLT0xMnT57EsmXLcPLkSfz666+wsrKq8r4Ur6XiPjTp1q0bACA2Nlan8xHVFg9nO4OWIwMQVC/l5uYKACI3N1en8g8ePBBJSUniwYMHBq/LwQu3RMu5P4sWFb5aPvo6eOGWwa9pCC1atBAARHR0tMr2JUuWCABi3LhxorS0VLl99erVAoAICQlROxfKWqaFjY2NmDBhgrh//75yX58+fQQA8cUXX6gcM2/ePAFAzJkzR+18xcXFet3LwIEDBQDx3Xffqe375JNPBAAREBCgtu/8+fMCgGjXrp3KdsVrY29vL77++mshl8uV+3744QcBQDRo0EBcvnxZub2kpES0b99eSCQScfDgQeX2oqIi5WsQGRmp0/0UFBQoX9O4uDiNZeLi4pRlCgsLdTqvMRnzb4zql1KZXAR9dkTj+6LivTHosyOiVCav+mSklT7PQXZjUY2Y20C84uJirFixAgDw2WefqbRCvPHGG7C2tkZ0dDSyszX3tT/33HOIjIyEo6OjcltQUBAA4O+//1YpW1BQAEBzfhYbG5ua3Ug5EydOhJWVFRISEvDHH3+o7Fu7di0Aza06ALBq1SpMnz5dpY4vvvgiAgMDUVpaiu3btyu379q1C8nJyQgJCUFYWJhyu62tLV5//XUAwE8//aRTnXNzc5XfOzk5aSxT/jUuX57I1KykEkQM8wcAVPzrVvwcMcwfVlLmZqot7MaiGtFnIF7v1o1qr2LVFB8fj5ycHFhbW+PTTz9V229tbY2SkhIkJyejd+/eavuXL1+uts3Z2RkA1FaaHz58OFavXo1//OMfuHfvHmbPno127doZ6E4e8/HxQVhYGPbv34+NGzdi9erVAID79+9j27ZtsLW1RXh4uMZj27Rpo3F7r169cPbsWZXg6ciRIwCAzMxMvPHGGyrlb9y4AQC4ePGi3vUXQnOgrG07UV0Q1skba8cFqnXve9XT7v36jsEO1Yi5DcS7fv06gLK1xL755hut5fLz8zVub9BA9z+pwYMHY+XKlZgzZw7Wr1+PDRs2ICwsDEuWLNE6TqW6Jk+ejP3792P79u344osvYGtri23btiEvLw9jx45Fo0b6BaKK8vfu3VNuU7x2iYmJSExM1HicttetIjc3N+X3ihawys5VvjxRXRHWyRtD/L1wJjUbmflF8HC2Q08/d7bomAC7sahGzG0gnmLWT7NmzSCE0Po1dOhQg1xvxowZuHTpEmbMmAFnZ2ccPHgQQUFB2LBhg0HOrzBs2DB4enoiOzsbe/bsAVD5wOSqKFqpHBwclNsUr93ChQu1vm63bt3S6fz29vZo3rw5gMdBVEXXrl0DALRo0QJ2dvXj3xdZHiupBL1bN8KIAB/0bt2IgY6JMNihGunp5w5vVzu1fmkFCcpmZfX0c6/NalVb69atAZR1uygepsbWvHlzrFy5EmlpaZg0aRJkMhlmzpyJnJwcg12jQYMGmDBhAgBg48aNOHXqFBISEvDEE0/oNOurIsVro3i9yn9/4sQJA9QYGDBgAICyrkVN4uLiAAB9+/Y1yPWIyHwx2KEaMbeBeAEBAfD19QUAfP3117V6bTc3N6xfvx6urq4oLCxUG9BcGcWK2/fv39daZsqUKQCAw4cPY9GiRQCq16pTUlKizNQ8ZMgQ5fbhw4cDKMv9k5SUpPd5Kxo/fjwA4IcfflAbnyOTybBz506VckRE2jDYoRpTDMTzclXtSvBytcPacYH1aiCeVCpVDkz+8ssvVWYbKVy/fl3rbCx97N69W631KDExEXl5eZBKpWjRooXO51K0quzfv1/rwN127dqhX79+kMvlOHz4MOzs7JStPdrMmDED58+fV9kWERGBzMxM+Pv745lnnlFuHzx4MEJDQyGXy/HSSy8hNTVV5Ti5XK41KaMmQ4cOVeYnqjhYfMGCBUhNTUVwcLDKzC8iIk04QJkMwpwG4o0fPx6XL1/GkiVLMHbsWCxduhTdunVDYWEhkpOTcf78eRw/fhz9+vWr0XU2bdqEUaNGISAgAJ07d0ZBQQEOHDgAIQQ++OADeHp66nyuyZMnY/369YiKikKPHj3QsWNHjBw5Es8//7xKuSlTpiiT8I0ePRoNGzas9LxSqRRPPvkkBgwYAD8/P+X0dTc3N2zfvl0tQeCOHTvw9NNPIz4+Hh06dEBISAiaNm2KjIwMxMfHw9nZGZcvX9b5vrZt24Z+/frhww8/xL59++Dv74+EhAQkJCSgdevW+P7773U+FxFZMINn+aFaUZeSCtZX2pIKKvz222/ihRdeEJ6ensLa2lp4eHiIPn36iPfff19kZGSolMWj5Hapqalq54mIiBAARHh4uMr2AwcOiHHjxom2bdsKBwcH0aRJExEcHCz27t1brfvZv3+/6NGjh3B0dBSNGzcW27ZtUytTUFAgbG1tBQDx+++/az2X4rU5evSoWLt2rejSpYuwtbUVjRs3FmPGjFFJJlhRUVGRWLlypejVq5dwdnYWdnZ2omXLlmLEiBHi22+/1fu+srOzxcyZM4Wvr6+wsbERfn5+Yvbs2SInJ0fvcxkT/8aIapc+z0GJEExWUR/l5eXB1dUVubm5cHFxqbJ8UVERUlNT4efnx5krFiw2Nhb9+/dHp06dKu1SatmyJdLS0hAdHY3g4ODaq2A9xr8xotqlz3OQ3VhEFkSRHbo6A5OJyDRkcmEWQwRMicEOkYVISUnBnj17YG9vj3Hjxpm6OkSkA3NaZNmUOBuLyEJ89dVXkMvlePnll5lxmKgeiEpMx/StZ9WW5MnILcL0rWcRlZhuoprVP2zZIbIQq1evVq6LVZWrV68atzJEVKmqFlmWoGyR5SH+XuzS0gFbdoiIiOoYfRZZpqox2CEiIqpjzG2RZVNjsENERFTHmNsiy6bGYIeIiKiOMbdFlk3NLIKd27dvY/Xq1Rg0aBBatmwJW1tbeHp6YuTIkcrU+IY4pipXr16FRCLR+uXk5FST2yQiIgthbossm5pZzMYKDw/HoUOH4OPjg6CgINjZ2SE+Ph579uzBvn37sGbNGrzxxhs1PkZX3t7eyhWgy2NWVSIi0pVikeWKeXa8mGdHb2axXMTixYsRFBSEoUOHQiIpi3LlcjkWLlyIpUuXwsbGBpcuXULz5s1rdExVrl69Cj8/PwwcOBAxMTEGvceKuFwEUd3CvzEyFmZQ1kyf56BZdGNFRETgqaeeUgYtQNlqzR9//DGaNGmChw8fYv/+/TU+hoiIqLZZSSXo3boRRgT4oHfrRgx0qsEsgh1trKys0LJlSwBAdrZuuQiqcwwRERHVXWYd7JSWluLKlSsAgBYtWhjtGCIiIqq7zDrY2bFjB7KysuDk5IRhw4YZ7ZiKjh07BicnJ7Rv3x7h4eH47bffdD62uLgYeXl5On1RzbRs2RISicTo46vIcGQyGeLi4nD79m2zvB4RGYfZBjtpaWmYNWsWgLIFEF1dXY1yTHmOjo6YMGECXnvtNYSEhCA/Px+bN2/GwIEDMXnyZMhksirPsXTpUri6ulb55evrq1fdiOojuVyO06dP4/PPP8ezzz4Ld3d39OjRA3/99VeVx+bk5OC9995TDhj28/PDe++9h9zcXKNcj4jqLrOYel7RtWvXEBoaiqysLCxduhSTJ082yjEVNWnSBJGRkcqfhRDYtWsXXnvtNWzcuBFeXl749NNPKz3H/Pnz8e6771Z5rby8PAY8pJSWlqacCWRO3a/Xrl1DUFAQAMDd3R1FRbqlxk9PT0ffvn2RmpqKwMBADBw4EBcuXMCKFSuwe/duxMbGwttbfdpuda9HRHWb2bXsJCQkICgoCNeuXcOGDRswb948oxyjC4lEghdeeAGrVq0CAKxcubLKN09bW1u4uLjo9EWkEB4ejpCQEERHR5u6Kgbl4uKCVatW4dy5c7h7967GAEWTMWPGIDU1FYsWLUJ8fDw2bdqE+Ph4RERE4MqVK3j11VcNej0iqtvMKtg5efIkgoODkZ+fjwMHDujUOlOdY/Q1cuRIAEBBQQH+/PNPg5+fyFy5u7vj7bffRpcuXVTSRFTm4MGDOHbsGNq1a4eIiAiVfREREWjfvj1iYmIQFRVlkOsRUd1nNsHOuXPnEBYWhocPH+KXX37BkCFDjHJMdZSWliq/N+tmcbkMSD0OXPix7P/yqsco1VWlpaVYtWoVAgICYG9vD3d3d4wYMQIXLlzQWF6xJMjVq1dx5coVhIeHw9vbGy4uLggJCUFCQoLG45KTkzFu3DjlkiWNGzdGUFAQjh8/rlM9Y2JiIJFIcOzYMQDApEmTlHX56KOP8Ouvv0IikcDW1hZZWVkaz7FlyxZIJBKMGTNGua384O1z585h1KhRaNKkCRwcHNCtWzeV7tqKbty4galTp8LHxwe2trZo1aoV5s+fj8LCQp3uqaa2bNkCABg9ejSkUtW3OIlEgtGjRwMANm/eXCv1ISLTM4tgJzs7G8OGDUNeXh7WrVuH3r17G+WY6lKstSWRSNC2bVujXcekkvYCX3UCIp8Dfppc9v+vOpVtr2dKS0vx/PPPY+bMmSgqKsKLL76Irl27Yu/evejTpw/OnTun9djjx48jICAAUVFRGDhwILp06YKYmBiEhIQgMzNTpeylS5fQo0cPbN++Ha1bt8bYsWMRFBSEtLQ0pKWl6VTXpk2bYtq0acruluDgYEybNg3Tpk1D9+7dERoailatWuHhw4fYtm2bxnOsW7cOADBt2jS1fb///jt69+6NCxcu4Omnn8bAgQORkJCAiRMn4pNPPlErf+HCBQQGBmLjxo3w9/fHSy+9BIlEgmXLluGZZ57RaZB+TSkCxe7du2vc361bNwCo9hp4RFQPCTMwfvx4AUB0797dqMcIIURoaKho3769GD9+vNq+3NxcUVpaqrLt0qVLolWrVgKAeP755/W6VmVyc3MFAJGbm6tT+QcPHoikpCTx4MEDg9VB6c89QkS4ChHhUuHLtezrzz2Gv6YBtGjRQgAQ0dHRKtuXLFkiAIhx48ap/D5Xr14tAIiQkBC1cwEQAISNjY2YMGGCuH//vnJfnz59BADxxRdfqBwzb948AUDMmTNH7XzFxcV63cvAgQMFAPHdd9+p7fvkk08EABEQEKC27/z58wKAaNeuncp2xWtjb28vvv76ayGXy5X7fvjhBwFANGjQQFy+fFm5vaSkRLRv315IJBJx8OBB5faioiLlaxAZGanXfVWk7XemUFBQoPxdxMXFaSwTFxenLFNYWFij65Vn1L8xIlKjz3Ow3s/GyszMxNatWwGUfSLXtnhneHi4svWmOscopKSkIC0tDV5eXmrld+3ahffeew+9evWCp6cnbt68iePHj6OoqAgBAQH45ptvqn2fdZZcBkTNRdmzoyIBQAJEzQM6PAtIrWq5cvorLi7GihUrAACfffYZrKwe1/mNN97Au+++i+joaGRnZ8Pd3V3t+Oeee06tiycoKAgnTpzA33//rbK9oKAAADSODbGxsanxvShMnDgRERERSEhIwB9//IEnn3xSuW/t2rUANLfqAMCqVaswZcoUlW0vvvgiAgMDcfbsWWzfvh0ffvghgLJ//8nJyQgNDUVYWJiyvK2tLV5//XWcOHECP/30EyZMmGCwe6uo/LRyJycnjWUcHR1Vytvb2xutPkRUN9T7YKewsBDi0VqmCQkJWsdGBAUFKQOX6hyjC19fX/j4+CA2NhaFhYVwc3NDUFAQXnrpJUyZMsWgD7A6I+0EkHerkgICyLtZVs6vf61Vq7ri4+ORk5MDa2trjWkCrK2tUVJSguTkZI3/NpYvX662zdnZGUBZIFXe8OHDsXr1avzjH//AvXv3MHv2bLRr185Ad/KYj48PwsLCsH//fmzcuBGrV68GANy/fx/btm2Dra0twsPDNR7bpk0bjdt79eqFs2fP4o8//lBuO3LkCICyDxMVP0DcuHEDAHDx4sUa34+uhJY1jrVtJyLzVe+DnZYtW+r95lWdYxSuXr2qdd+gQYNw/vz5ap233rqvY2ZZXcuZ2PXr1wEAJSUllbbE5efna9zeoIHuf1KDBw/GypUrMWfOHKxfvx4bNmxAWFgYlixZonW8SXVNnjwZ+/fvx/bt2/HFF1/A1tYW27ZtQ15eHsaOHYtGjRrpdT5F+Xv37im3KV67xMREJCYmajxO2+tmKG5ubsrvFS1nldWhfHkiMl9mMUCZTMjJ07DlTEwxe6dZs2YQQmj9Gjp0qEGuN2PGDFy6dAkzZsyAs7MzDh48iKCgIGzYsMEg51cYNmwYPD09kZ2djT179gCofGByVRStVA4ODsptitdu4cKFWl+3W7cqawWsOXt7ezRv3hzA4+CromvXrgEoW/vOzs7OqPUhorqBwQ7VTIs+gEtTANpykkgAF5+ycvVA69atAZR1uygeisbWvHlzrFy5EmlpaZg0aRJkMhlmzpyJnJwcg12jQYMGyrEyGzduxKlTp5CQkIAnnngC/fvr372oeG0Ur1f570+cOGGAGlffgAEDAJR1SWoSFxcHAOjbt2+t1YmITIvBDtWM1AoIU4xTqRjwPPo5bFm9GJwMAAEBAcplOL7++utavbabmxvWr18PV1dXFBYWqg1oroy1tTWAsnE42igGGh8+fBiLFi0CUL1WnZKSEmWm5vK5qYYPHw6gLPdPUlKS3uc1lPHjxwMAfvjhB7XuaplMhp07d6qUIyLzx2CHas5/ODB6M+BSIbW+S9Oy7f7DTVOvapBKpcqByV9++SW2b9+uVub69evIzs6u8bV2796t1nqUmJiIvLw8SKVSvda4UrSq7N+/X+t4tHbt2qFfv36Qy+U4fPgw7OzsqpwZNWPGDLVxaBEREcjMzIS/vz+eeeYZ5fbBgwcjNDQUcrkcL730ElJTU1WOk8vlWpMyGtLQoUMREhKC5ORktUHmCxYsQGpqKoKDg1VmjBGReav3A5SpjvAfXja9PO1E2WBkJ8+yrqt60qJT3vjx43H58mUsWbIEY8eOxdKlS9GtWzcUFhYiOTkZ58+fx/Hjx9GvX78aXWfTpk0YNWoUAgIC0LlzZxQUFODAgQMQQuCDDz6Ap6fu45wmT56M9evXIyoqCj169EDHjh0xcuRIPP/88yrlpkyZokymN3r0aDRs2LDS80qlUjz55JMYMGAA/Pz8lNPX3dzcsH37dpWp+QCwY8cOPP3004iPj0eHDh0QEhKCpk2bIiMjA/Hx8XB2dsbly5d1vq+srCwsWLBA5WcAWLFiBXbs2AGgbLr/c889p3Lctm3b0K9fP3z44YfYt28f/P39lTMvW7duje+//96g1yOiOs6gGX6o1tSppIL1VFUJ43777TfxwgsvCE9PT2FtbS08PDxEnz59xPvvvy8yMjJUyuJRkrrU1FS180RERAgAIjw8XGX7gQMHxLhx40Tbtm2Fg4ODaNKkiQgODhZ79+6t1v3s379f9OjRQzg6OorGjRuLbdu2qZUpKCgQtra2AoD4/ffftZ5L8docPXpUrF27VnTp0kXY2tqKxo0bizFjxqgkE6yoqKhIrFy5UvTq1Us4OzsLOzs70bJlSzFixAjx7bff6nVPqampytdW21dERITGY7Ozs8XMmTOFr6+vsLGxEX5+fmL27NkiJyfHKNfj3xhR7dLnOSgRgkkn6qO8vDy4uroiNzdXpxXQi4qKkJqaCj8/P85AsWCxsbHo378/OnXqVGmXUsuWLZGWlobo6GgEBwfXXgXrMf6NEdUufZ6DHLNDZEEU2aGrMzCZiKi+YrBDZCFSUlKwZ88e2NvbY9y4caauDhFRrWGwQ2QhvvrqK8jlcrz88svMHExEFoWzsYgsxOrVq5XrYlWlsmVRiIjqG7bsEBERkVljsENERERmjcEOERERmTUGO0RERGTWGOwQERGRWWOwQ0RERGaNwQ4RERGZNQY7REREZNYY7BAREZFZY7BDREREZo3BDlmsli1bQiKRICYmxtRVIR3JZDLExcXh9u3bpq4KEdUjDHaIqM6Sy+U4ffo0Pv/8czz77LNwd3dHjx498Ndff1V5bE5ODt577z34+fnBzs4Ofn5+eO+995Cbm6v1WjExMXj99dfRtWtXuLm5wcHBAZ07d8bixYtRUFBg6NujOkQmFziZkoU9CTdxMiULMrkwdZXIgLgQKJEZSEtLQ2pqKvz8/NCiRQtTV8dgrl27hqCgIACAu7s7ioqKdDouPT0dffv2RWpqKgIDAzFw4EBcuHABK1aswO7duxEbGwtvb2+VY86cOYOQkBA0aNAAQUFBGDlyJDIzMxEdHY2PPvoIP/74I44dOwZ3d3eD3yeZVlRiOhbvS0J67uN/X96udogY5o+wTt6VHEn1BVt2iMxAeHg4QkJCEB0dbeqqGJSLiwtWrVqFc+fO4e7du2oBijZjxoxBamoqFi1ahPj4eGzatAnx8fGIiIjAlStX8Oqrr6od4+joiA8//BA3btzA8ePHsWnTJhw4cAB//vknvL29kZiYiAULFhj6FsnEohLTMX3rWZVABwAycoswfetZRCWmm6hmZEgMdoioznJ3d8fbb7+NLl26QCKR6HTMwYMHcezYMbRr1w4REREq+yIiItC+fXvExMQgKipKZV/nzp2xZMkSeHp6qmxv1aoV5s+fDwDYtWtXDe6G6hqZXGDxviRo6rBSbFu8L4ldWmaAwQ4ZjEwuw/8y/ocDVw7gfxn/g0wuM3WVqq20tBSrVq1CQEAA7O3t4e7ujhEjRuDChQsay0skEkgkEly9ehVXrlxBeHg4vL294eLigpCQECQkJGg8Ljk5GePGjUPLli1ha2uLxo0bIygoCMePH9epnjExMZBIJDh27BgAYNKkScq6fPTRR/j1118hkUhga2uLrKwsjefYsmULJBIJxowZo9xWfvD2uXPnMGrUKDRp0gQODg7o1q0bIiMjtdbpxo0bmDp1Knx8fGBra6sMFgoLC3W6p5rasmULAGD06NGQSlXf4iQSCUaPHg0A2Lx5s87nbN26NQAgOzvbQLWkuuBMarZai055AkB6bhHOpPL3Xt9xzA4ZxJG0I1h2ZhluFz6eJePp4Il5PedhcIvBJqyZ/kpLS/H888/j559/Rvv27fHiiy/ixo0b2Lt3L44ePYrY2Fh07dpV47HHjx/HW2+9BXt7e4SEhODGjRuIiYlBSEgIkpOT4eHhoSx76dIl9OjRA/fv30dISAhCQ0ORmZmJ+Ph4pKWloX///lXWtWnTppg2bRr27t2L9PR0BAcHo3379gCA7t27IzQ0FK1atcKVK1ewbds2zJgxQ+0c69atAwBMmzZNbd/vv/+OTz/9FD4+Pnj66adx584d/PLLL5g4cSKuX7+OhQsXqpS/cOECBg0ahKysLISGhsLT0xMnT57EsmXLcPLkSfz666+wsrKq8r5qQhEodu/eXeP+bt26AQBiY2N1Pufff/8NAGY1HoqAzHzdxoDpWo7qMEH1Um5urgAgcnNzdSr/4MEDkZSUJB48eGDwuhy+elh03tRZdNrUSeWr86bOovOmzuLw1cMGv6YhtGjRQgAQ0dHRKtuXLFkiAIhx48aJ0tJS5fbVq1cLACIkJETtXCj7EChsbGzEhAkTxP3795X7+vTpIwCIL774QuWYefPmCQBizpw5aucrLi7W614GDhwoAIjvvvtObd8nn3wiAIiAgAC1fefPnxcARLt27VS2K14be3t78fXXXwu5XK7c98MPPwgAokGDBuLy5cvK7SUlJaJ9+/ZCIpGIgwcPKrcXFRUpX4PIyEi97qsibb8zhYKCAuXvIi4uTmOZuLg4ZZnCwsIqr/nw4UPxxBNPCADiww8/1FrOmH9jZBwnLt8VLeb+XOXXict3TV1V0kCf5yC7sahGZHIZlp1ZBqGh11uxbfmZ5fWmS6u4uBgrVqwAAHz22WcqrRBvvPEGrK2tER0drbU747nnnkNkZCQcHR2V2xSziRStAwqKqcyaxqLY2NjU7EbKmThxIqysrJCQkIA//vhDZd/atWsBaG7VAYBVq1Zh+vTpKnV88cUXERgYiNLSUmzfvl25fdeuXUhOTkZISAjCwsKU221tbfH6668DAH766SeD3Zcm5aeVOzk5aSxT/nejbRp6eR988AH++usvPPHEE5gzZ07NK0l1Rk8/d3i72kHbaDAJymZl9fTjDLz6jt1YVCNnM8+qdF1VJCCQUZiBs5ln0cOrRy3WrHri4+ORk5MDa2trfPrpp2r7ra2tUVJSguTkZPTu3Vtt//Lly9W2OTs7AygLpMobPnw4Vq9ejX/84x+4d+8eZs+ejXbt2hnoTh7z8fFBWFgY9u/fj40bN2L16tUAgPv372Pbtm2wtbVFeHi4xmPbtGmjcXuvXr1w9uxZleDpyJEjAIDMzEy88cYbKuVv3LgBALh48WKN70dXQmgeVKptuyaffvopvvjiC7Rp0waHDh1S/i7JPFhJJYgY5o/pW89CAqh8ZFMEQBHD/GEl1W1wPNVdDHaoRu4U3jFoOVO7fv06AKCkpATffPON1nL5+fkatzdooPuf1ODBg7Fy5UrMmTMH69evx4YNGxAWFoYlS5ZoHW9SXZMnT8b+/fuxfft2fPHFF7C1tcW2bduQl5eHsWPHolGjRnqdT1H+3r17ym2K1y4xMRGJiYkaj9P2uhmKm5ub8nttSQDL16F8+fLkcjlmzJiBNWvWoFevXti7d6/KeCsyH2GdvLF2XKBanh0v5tkxK+zGohpp4tDEoOVMTTF7p1mzZhBCaP0aOnSoQa43Y8YMXLp0CTNmzICzszMOHjyIoKAgbNiwwSDnVxg2bBg8PT2RnZ2NPXv2AKh8YHJVFK1UDg4Oym2K127hwoVaX7dbt27V9FYqZW9vj+bNmwN4HHxVdO3aNQBlg43t7OzU9peWlmL8+PFYs2YNRo4ciZiYGAY6Zi6skzdi54bi+6lBWPlKAL6fGoTYuaEMdMwIgx2qkUCPQHg6eEKipddbAgm8HLwQ6BFYyzWrHsUU4xs3bigfisbWvHlzrFy5EmlpaZg0aRJkMhlmzpyJnJwcg12jQYMGmDBhAgBg48aNOHXqFBISEvDEE0/oNOurIsVro3i9yn9/4sQJA9S4+gYMGACgrEtSk7i4OABA3759Ne6fOnUqtm/fjhdffBE//vijxoCIzI+VVILerRthRIAPerduxK4rM8Ngh2rESmqFeT3nAYBawKP4eW7PubCSGne6saEEBATA19cXAPD111/X6rXd3Nywfv16uLq6orCwUG1Ac2Wsra0BlI3D0WbKlCkAgMOHD2PRokUAqteqU1JSoszUPGTIEOX24cOHAyjL/ZOUlKT3eQ1l/PjxAIAffvhBbXyOTCbDzp07VcqV949//AObNm1Cx44dsXnzZqNPkyei2sFgh2pscIvBWBG8Ah4Oqk39ng6eWBG8ol7l2ZFKpcqByV9++aXKbCOF69evGyS53O7du9VajxITE5GXlwepVKpXThdFq8r+/fu1DsBt164d+vXrB7lcjsOHD8POzk7Z2qPNjBkzcP78eZVtERERyMzMhL+/P5555hnl9sGDByM0NBRyuRwvvfQSUlNTVY6Ty+VakzIa0tChQ5V5jSoOMl+wYAFSU1MRHBysMmMMKBs8rVgOYvny5bC3tzd6XYmodnCAMhnE4BaDEeIbgrOZZ3Gn8A6aODRBoEdgvWnRKW/8+PG4fPkylixZgrFjx2Lp0qXo1q0bCgsLkZycjPPnz+P48ePo169fja6zadMmjBo1CgEBAejcuTMKCgpw4MABCCHwwQcfqC1bUJnJkydj/fr1iIqKQo8ePdCxY0eMHDkSzz//vEq5KVOmKJPpjR49Gg0bNqz0vFKpFE8++SQGDBgAPz8/5fR1Nzc3bN++Xa3lY8eOHXj66acRHx+PDh06ICQkBE2bNkVGRgbi4+Ph7OyMy5cv63xfWVlZKutRKbJAr1ixAjt27ABQNt3/ueeeUzlu27Zt6NevHz788EPs27cP/v7+SEhIQEJCAlq3bo3vv/9e7Vr/+te/UFJSAgD473//i3379qmVadSokcZZekQmJZcBaSeA+7cBJ0+gRR+gHr73GpXBs/xQrahLSQXrq6oS1P3222/ihRdeEJ6ensLa2lp4eHiIPn36iPfff19kZGSolMWjJHWpqalq54mIiBAARHh4uMr2AwcOiHHjxom2bdsKBwcH0aRJExEcHCz27t1brfvZv3+/6NGjh3B0dBSNGzcW27ZtUytTUFAgbG1tBQDx+++/az2X4rU5evSoWLt2rejSpYuwtbUVjRs3FmPGjFFJJlhRUVGRWLlypejVq5dwdnYWdnZ2omXLlmLEiBHi22+/1eueUlNTla+ttq+IiAiNx2ZnZ4uZM2cKX19fYWNjI/z8/MTs2bNFTk6OxvLh4eFVXqtFixZa68q/MTKJP/cI8WUHISJcHn992aFsu5nT5zkoEUKPpBNUZ+Tl5cHV1RW5ublwcXGpsnxRURFSU1Ph5+fHAZcWLDY2Fv3790enTp0q7VJq2bIl0tLSEB0djeDg4NqrYD3GvzGqdUl7gZ0TALWkro/GT47eDPgPr+1a1Rp9noMcs0NkQRTZoaszMJmI6hC5DIiaC/VAB4+3Rc0rK0cMdogsRUpKCvbs2QN7e3uMGzfO1NUhoppIOwHkVZa3SgB5N8vKEYMdIkvx1VdfQS6X4+WXX9aaOZiI6on72pfpqVY5M8fZWEQWYvXq1cp1sapy9epV41aGiGrGScfZmrqWM3Nm0bJz+/ZtrF69GoMGDULLli1ha2sLT09PjBw5UjnNVpsDBw4gNDQUrq6ucHV1RWhoKA4ePFij+mzduhVBQUFwcnJCo0aNMGzYMJw6dapG5yQiItORyQVOpmRhT8JNnEzJgkxu4rk9LfoALk2BytZsd/EpK0fmEeyEh4djxowZSE5ORvfu3fHSSy/B3d0de/bswcCBA5VrAFW0bNkyPPvss4iLi8NTTz2Fp556CnFxcXjmmWc0rl6tizfffBPjx49HSkoKRowYgX79+uHw4cPo16+fxgR1RERUt0UlpqPf8qMYs/4UZu5IwJj1p9Bv+VFEJaabrlJSKyBM8ZyqGPA8+jlsGfPtPGIWU88XL16MoKAgDB06FBJJ2S9ZLpdj4cKFWLp0KWxsbHDp0iXlAoEAEB0djUGDBsHHxwe///67yuKBffv2xY0bN/Drr78iJCRE53ps2rQJkyZNQteuXREdHa1M2Hb+/Hn069cPxcXFuHDhAtq1a1fje+bUc6K6hX9j5ikqMR3Tt57VNrkba8cFmnbB0KS9ZbOyyg9WdvEpC3TMeNo5oN9z0CyCHW1kMhm8vb1x584dfP3115g+fbpyX69evXDmzBls3boVY8eOVTlu+/btGDt2LHr16qVz91NpaSlatGiBW7duITY2Vm2Rwc8++wwLFizAyy+/rMz8WhMMdojqFv6NmR+ZXKDf8qNIzy3SuF8CwMvVDrFzQ027cKiFZlBmnp1HrKys0LJlSwBQWcvo77//xpkzZ2BjY4NRo0apHff888/DxsYGp0+f1nkxxqNHj+LWrVvw9fXVuJryK6+8AqBsPaS8vLxq3A0REdWmM6nZWgMdoCybTXpuEc6k1nytvBqRWgF+/YHOL5b93wICHX2ZdbBTWlqKK1euAIDKoorHjx8HAPj7+2tc7M/e3h7+/v4AUOUA54rn7Natm8b9rVq1gpubG4qLixEXF6f7TRARkUlk5msPdKpTjkzHrIOdHTt2ICsrC05OThg2bJhye3JyMgCgWbNmWo9V7FOUrYqhzllcXIy8vDydvoiIyHg8nHXrjrx6t8DINaGaMttgJy0tDbNmzQJQlkzN1dVVuS83NxcA4OTkpPV4R0dHlbJVMdQ5ly5dqpwGX9mXr6+vTvUiIqLq6ennDi+XqgOe789cM/1UdKqUWQY7165dQ2hoKLKysrB06VJMnjxZY7nKxmZXd9x2Tc85f/585ObmVvl1/fr1atWPiIh0YyWVYEzP5lWWy8grNv24HaqU2WVQTkhIwDPPPIM7d+5gw4YNGgMdRar8ggLtTY/5+fkqZatiqHPa2trC1tZWp2sSEZFxtWzsoFM5jtup28yqZefkyZMIDg5Gfn4+Dhw4oLVFp0OHDgBQaevItWvXVMpWRZdzKvbpek4yrpYtW0IikSAmJsbUVSEdyWQyxMXF4fZtrvdDtUPXcTu6liPTMJtg59y5cwgLC8PDhw/xyy+/YMiQIVrL9u/fHwDw119/4cGDB2r779+/rxxErGkauSYDBgwAAMTHx2vcf/HiRdy/fx92dnZaZ2wRkSq5XI7Tp0/j888/x7PPPgt3d3f06NEDf/31V5XH5uTk4L333lPmvfHz88N7771X6Zi55cuXY/DgwWjWrBlsbW3h6OiIJ554Av/3f/+n/ABElqWnnzu8Xe0qW5QB3q526OnnXpvVIj2ZRbCTnZ2NYcOGIS8vD+vWrUPv3r0rLd+mTRv07t0bDx8+xH//+1+1/Tt37kRpaSl69eqFtm3b6lSH4OBgNGvWDDdu3MCJEyfU9iuWihgxYgScnZ11OieRrtLS0hATE4O0tDRTV8Wgrl27hqCgIMydOxenTp1CUZFuXQXp6ekIDAzEihUr4O7ujldeeQXu7u5YsWIFAgMDkZ6uOc1/REQEkpKS0LlzZ7z66qsYPHgwbty4gTVr1qBbt264ceOGIW+P6gErqQQRw8pSkWhZlAERw/xNm1SQqibMwPjx4wUA0b17d52PiY6OFhKJRDRv3lzcuHFDuf3KlSvCy8tLSCQScfToUbXjQkNDRfv27cX48ePV9m3atEkAEIGBgSInJ0e5PS4uTjg4OAgbGxuRnJys591plpubKwCI3Nxcnco/ePBAJCUliQcPHhjk+uagRYsWAoCIjo42dVVqbODAgQKA+O6770xdFYPKysoSq1atEufOnRNyuVzn35ni9Vi0aJHK9oiICAFABAcHazzuf//7n5DL5Srb7ty5I1q1aiUAiI8++kjrNfk3Zt4OXrglgj47IlrM/Vn5FfTZEXHwwi1TV81i6fMcrPcDlDMzM7F161YAZUkE33jjDY3lwsPDVVp8goODsXTpUsybNw8dO3bE008/jdLSUhw8eBAFBQVYvny5xnWxUlJSkJaWBi8vL43XOH36NNauXYv27dtjyJAhyM3NxaFDhyCXy7F582aDrItFZCnc3d3x9ttv63XMwYMHcezYMbRr1w4REREq+yIiIrBjxw7ExMQgKioKYWFhKvu7d++udr7GjRtj5MiRWLFiBe7cuaP/TZBZCOvkjSH+XjiTmo3M/CJ4OJd1XbFFp56oheDLqFJTUwXKsnZX+qXtE+/PP/8sBgwYIJycnISrq6sYNGiQiIqK0no9xSfLgQMHai0TGRkpunfvLhwcHIS7u7sYPny4OH36dA3vVBVbdmquslaCkpISsXLlStG1a1dhZ2cnGjZsKIYPHy7Onz+v8VyKf2epqakiJSVFTJgwQXh5eQlnZ2cRHBws/vjjD43HXbx4UYwdO1a0aNFC2NjYiEaNGolevXqJ3377Tad7iI6O1vpvPiIiQhw5ckQAEDY2NuLu3bsaz7F582YBQLzyyisaX5uEhATx/PPPi8aNGwt7e3sRGBgoNm3apLVO169fF1OmTBFNmzYVNjY2ws/PT8ybN08UFBTodE+V0aVlZ8yYMQKAWLhwocb9H374oQAgxowZo/N1FeesrOWMf2NEtUuf52C9D3YsVV0MduSlpeL+qdMiZ9/P4v6p00JeWmq0axmCtgdnSUmJeO655wQA0b59ezFu3DgRHBwsAAgnJyeRkJCgdi5FgLF582bh7OwsPDw8xMsvvyz69u0rAAg3Nzdx+/ZtlWP+/vtv4ezsLCQSiQgNDRWTJk0Szz77rPDy8hJbtmzR6R6Sk5PFtGnThLe3t7J7Ztq0aWLatGli3759Qi6XK7tgVq5cqfEcffr0UXsdFK/NJ598Iuzt7UWbNm3E+PHjRVhYmJBKpQKA+Pjjj9XOdf78edGkSRMhlUrF4MGDxdixY5XXHzhwoCit4b8JXYKdZs2aCQBi9+7dGvfv3r1bABC+vr5VXk8mk4l///vfwsrKSoSEhFRafwY7RLWLwY4FqGvBTu6hQ+LvgcEiqX0H5dffA4NF7qFDRrmeIWh7cC5ZskQAEOPGjVN5uK1evVoAECEhIWrnUgQ7NjY2YsKECeL+/fvKfYpg4osvvlA5Zt68eQKAmDNnjtr5iouL9bqXysbsfPLJJwKACAgIUNt3/vx5AUC0a9dOZbvitbG3txdff/21yjiWH374QQAQDRo0EJcvX1ZuLykpEe3btxcSiUQcPHhQub2oqEj5GkRGRup1XxVVFewUFBQofxdxcXEay8TFxSnLFBYWqu1fs2aNmDx5shg+fLjw9vYWPj4+YunSpaKkpKTSujHYIapd+jwHzWI2FplW3i+/4ObMWSjNyFDZXnr7Nm7OnIW8X34xUc30V1xcjBUrVgAAPvvsM1hZPV49+I033oC1tTWio6ORna05W+pzzz2HyMhI5dIgABAUFAQA+Pvvv1XKKhJQSiTqff42NjY1u5FyJk6cCCsrKyQkJOCPP/5Q2bd27VoAwLRp0zQeu2rVKkyfPl2lji+++CICAwNRWlqqnGUIALt27UJycjJCQkJUxsLY2tri9ddfBwD89NNPBrsvTcpPK9e2dEv5342maegHDhzAv//9b+zduxfp6ekoKSlBRkYG7t69a/gKE1GtqPcDlMm0hEyG258tBTQthSEEIJHg9mdL4TxoECTlAoe6Kj4+Hjk5ObC2tsann36qtt/a2holJSVITk7WmOJg+fLlatsUqQaKi4tVtg8fPhyrV6/GP/7xD9y7dw+zZ882ygB2Hx8fhIWFYf/+/di4cSNWr14NoCyf1LZt22Bra4vw8HCNx7Zp00bj9l69euHs2bMqwdORI0cAlE0aqDhRQDFl++LFizW+H10JLcuzaNuu8PPPP6O0tBR37txBXFwcVq1ahZUrV2LHjh04cuQIOnXqZIzqEpERMdihGimMi1dr0VEhBEozMlAYFw/HXj1rr2LVpMhyXVJSgm+++UZrOcXSHxU1aKD7n9TgwYOxcuVKzJkzB+vXr8eGDRsQFhaGJUuWaJwVVBOTJ0/G/v37sX37dnzxxRewtbXFtm3bkJeXh7Fjx6JRo0Z6nU9R/t69e8ptitcuMTERiYmJGo/T9roZSvmlWLQt3VK+DtqWbmnQoAG8vb0xbNgwPPfccxg9ejR+/PFHvPXWWzh27Jghq0xEtYDdWFQjpTpOxdW1nKlJpWV/Es2aNYMoG9Om8Wvo0KEGud6MGTNw6dIlzJgxA87Ozjh48CCCgoKwYcMGg5xfYdiwYfD09ER2djb27NkDAFi3bh0A7V1YlVG0Ujk4PF43SPHaLVy4UOvrduvWrZreSqXs7e3RvHnZwo3alm5RZEJu0aIF7OyqTvEvkUgwc+ZMAEBsbKzOiQ2JqO5gsEM10qBJE4OWM7XWrVsDKOt2qa3lAZo3b46VK1ciLS0NkyZNgkwmw8yZM5GTk2OwazRo0AATJkwAAGzcuBGnTp1CQkICnnjiCeXyKfpQvDaK16v895oyiNemqpZuiYuLA6D7UjAA4OrqCqBs+YqsrKwa1pCIahuDHaoRh+7d0MDLC9AwyBYAIJGggZcXHLrXj/XAAgIC4OvrCwD4+uuva/Xabm5uWL9+PVxdXVFYWKg2oLky1tbWAMrG4WgzZcoUAMDhw4exaNEiANVr1SkpKUF0dDQAqKxBN3z4cABATEwMkpKS9D6voYwfPx4A8MMPP6iNz5HJZNi5c6dKOV3ExsYCKBt/5enpaaCaUnXJ5AInU7KwJ+EmTqZkQSavfBwWEYMdqhGJlRU8P5j/6IcKAc+jnz0/mF8vBicDZV0xioHJX375pcpsI4Xr169rnY2lj927d6u1HiUmJiIvLw9SqRQtWrTQ+VyKVpX9+/drHYDbrl079OvXD3K5HIcPH4adnZ2ytUebGTNm4Pz58yrbIiIikJmZCX9/fzzzzDPK7YMHD0ZoaCjkcjleeuklpKamqhwnl8tx4cIFne+puoYOHYqQkBAkJyerDTJfsGABUlNTERwcrJY9+c6dOzh69ChkMpnK9pMnT+KDDz4AAIwbN06vcVlkeFGJ6ei3/CjGrD+FmTsSMGb9KfRbfhRRiZrXOyMCOECZDMBl6FBg5Ve4/dlSlcHKDTw94fnB/LL99cj48eNx+fJlLFmyBGPHjsXSpUvRrVs3FBYWIjk5GefPn8fx48fRr1+/Gl1n06ZNGDVqFAICAtC5c2cUFBTgwIEDEELggw8+0KsFYfLkyVi/fj2ioqLQo0cPdOzYESNHjsTzzz+vUm7KlCnKVorRo0ejYcOGlZ5XKpXiySefxIABA+Dn56ecvu7m5obt27erTM0HgB07duDpp59GfHw8OnTogJCQEDRt2hQZGRmIj4+Hs7MzLl++rPN9ZWVlYcGCBSo/A8CKFSuwY8cOAGXT/Z977jmV47Zt24Z+/frhww8/xL59++Dv74+EhAQkJCSgdevW+P7779WudfPmTQwaNAiNGjVCt27d4OXlhZSUFJw4cQJCCPTp00fjbDuqPVGJ6Zi+9SwqhvMZuUWYvvUs1o4LRFgnb5PUjeo2BjtkEC5Dh8J50KCy2Vl37qBBkyZw6N6t3rToVLR48WLlbKnY2Fhs374dDRs2RJs2bfD++++jbdu2Nb7GtGnT4OzsjNOnT+PHH3+Eo6MjevXqhXfffRfDhg3T61w9evTAvn378NFHHyEpKQlpaWl46qmn1Mq99NJLmDZtGoqLi3XqwvrnP/+J5ORkrF27FidPnoSzszPGjBmDjz/+WGW8jkKTJk3w+++/45tvvsH27dtx4sQJlJSUwMvLC71798azzz6r133l5+drnBW3b98+5fdeXl5qwY63tzfi4uKwePFi7Nq1CwkJCfDx8cHs2bOxcOFC5Ric8ry8vPDaa6/h3LlziI+PR25uLpycnNC3b1+MGTMGr7/+Olt1TEgmF1i8L0kt0AHKMkRKACzel4Qh/l5cr4rUSERVSSeoTsrLy4Orqytyc3Ph4uJSZfmioiKkpqbCz89PpxkoZJ5iY2PRv39/dOrUqdIupZYtWyItLQ3R0dEIDg6uvQrWY/wbM66TKVkYs/5UleW+nxqE3q31S6VA9ZM+z0GO2SGyIIrs0NUZmExkSpn5uk3517UcWRa2yRJZiJSUFOzZswf29vYYN26cqatDpBcPZ91ay3QtpyuZXOBMajYy84vg4WyHnn7u7CarhxjsWBj2Wlqur776CnK5HC+//LLWzMFUffzbMq6efu7wdrVDRm6RxnE7EgBermXBiKFEJaZj8b4kpOc+bi3ydrVDxDB/DoSuZ9iNZSEU2W0rTqsly7F69WoIIfDdd99VWfbq1asQQnC8jh4Uf1uKvzUyLCupBBHD/AGUBTblKX6OGOZvsFYXxcyv8oEO8HjmF6e61y/8q7QQ1tbWsLKywoMHD0xdFSKz9ODBA1hZWSkTPJLhhXXyxtpxgfByVe2q8nK1M+i086pmfgFlM7+MncyQyRMNh91YFkIikcDBwQG5ublwd3dXy49CRNUnk8mQm5sLBwcHSLRlEyeDCOvkjSH+XkYdR3MmNVutRac8ASA9twhnUrONNvOLXWiGxWDHgnh4eODq1atIS0uDu7s7bG1t+cZMVANCCBQXFyM7OxtyuRweHh6mrpJFsJJKjDq93NQzv5g80fAY7FgQGxsbNGvWDHfv3kV6OvubiQzF0dERXl5esLGxMXVVyABMNfMLYPJEY2GwY2EcHBzQvHlzlJaWorS01NTVIar3GjRowMzKZsYUM78U6kIXmjniX6iF4hs0EZFmiplf07eehQRQCXiMMfOrPFN3oZkrzsYiIiKqoLZmflVkyi40c8aP9kRERBrUxsyvikzZhWbOGOwQERFpYeyZX5quZ6ouNHPGbiwiIqI6xFRdaOaMLTtERER1jCm60MwZgx0iIqI6qLa70MwZu7GIiIjIrDHYISIiIrPGYIeIiIjMGoMdIiIiMmsMdoiIiMisMdghIiIis8Zgh4iIiMwagx0iIiIyawx2iIiIyKwx2CEiIiKzxmCHiIiIzBqDHSIiIjJrDHaIiIjIrDHYISIiIrPGYIeIiIjMmtkGO3fu3MHJkydNXQ0iIiIyMbMJdjIzM7Fz5068+eab6NixIzw8PDB//nyt5SdOnAiJRFLl18SJE3Wuw9WrVys9l5OTkwHulIiIiPTRwNQVMJT3338fkZGRkEqlaNy4cZXlQ0JCYGdnp3X/6dOnkZCQABcXF73r4u3tjeHDh6ttr+x6REREZBwSIYQwdSUMYffu3ZBKpejfvz/27NmDSZMmYeDAgYiJidH7XPn5+Wjfvj0KCwuRlJSEpk2b6nTc1atX4efnV+3r6iMvLw+urq7Izc2tVkBGRERUn+nzHDSblp2RI0ca7FwRERFIT0/HunXrdA50iIjIfMnkAmdSs5GZXwQPZzv09HOHlVRi6mqRjswm2DGUxMRErF69Gr169cLrr79u6uoQEZGJRSWmY/G+JKTnFim3ebvaIWKYP8I6eZuwZqQrsxmgbChvvvkm5HI51qxZA4mEUTsRkSWLSkzH9K1nVQIdAMjILcL0rWcRlZhuopqRPtiyU86WLVtw/PhxTJ48Gd26dav2eY4dOwYnJyf4+PggKCgIkydPxoABA3Q6tri4GMXFxVWWy8vLq3b9iIioajK5wOJ9SdA0sFUAkABYvC8JQ/y92KVVx7Fl55Hc3Fy8//77cHR0xMcff1ytczg6OmLChAl47bXXEBISgvz8fGzevBkDBw7E5MmTIZPJqjzH0qVL4erqWuWXr69vtepIRES6OZOardaiU54AkJ5bhDOp2bVXKaoWtuw8smjRImRkZOCDDz6At3f1+mCbNGmCyMhI5c9CCOzatQuvvfYaNm7cCC8vL3z66aeVnmP+/Pl49913q7xWXl4eAx4iIiPKzNce6FSnHJkOgx2UDUpes2YNnJ2d8d577xnsvBKJBC+88ALu37+PiRMnYuXKlfjwww8rzbdja2sLW1tbg9WBiIiqx8NZt9xoupYj02E3FoB33nkHMpkMU6dOhbu7u8HPr5gWX1BQgD///NPg5yciIsPr6ecOb1c7aBuNI0HZrKyefoZ/bpBhWXyw8+uvv+LIkSOQSqWYOXOmUa5RWlqq/L6oiM2dRET1gZVUgohh/hoHKANlY3YihvlzcHI9YPHBzvLlywEAQ4cORfPmzY1yjdjYWABl3Vpt27Y1yjWIiIhIM4sOdtLT0/Hrr78CAF599VWdjhk0aBA6dOiACRMmqO3Ly8tTm3F1+fJl5YDjkSNHwsPDo4a1JiKi2qCYeq6NYuq5TG4Wqy6pkMkFTqZkYU/CTZxMyar392g2A5QjIyNx8uRJAEBycjIA4O+//8Ybb7yhLLNu3TqVYw4ePAi5XA6JRIJnnnlGp+ukpKQgLS0NXl5eavt27dqF9957D7169YKnpydu3ryJ48ePo6ioCAEBAfjmm2+qe3tERFTL9Jl63rt1o9qrmJGZY8Zoswl2oqOjVaZ9A2UtN+UDjIrBzunTpwEAbdu2RaNGNf+H6uvrCx8fH8TGxqKwsBBubm4ICgrCSy+9hClTpsDGxqbG1yAiotphiVPPFRmjK7bjKDJGrx0XWC8DHrNZ9dzScNVzIiLjOpmShTHrT1VZ7vupQWbRsiOTC/RbflRra5YEgJerHWLnhtaJQdn6PActeswOERGRNt1aNERVz3SppKycOTDnjNEMdoiIiDSIT7uHqsblykVZOXNgzt12Rhmzc/v2bVy8eBHZ2dkoKiqCs7MzPD094e/vD0dHR2NckoiIyKDM+eGviTlnjDZYsHP48GH85z//wYEDB3D79m2NZSQSCTp16oThw4djwoQJaNOmjaEuT0REZFDm/PDXRJExOiO3SGMiRcWYnfqYMbrG3Vg//vgj/P39ERYWhu+++w4ZGRkQQmj8ksvlOH/+PD799FN06NABI0aMwF9//WWI+yAiIjIoS1suQpExGoDaPSt+rq8Zo6s9Gys9PR3jx49HdHQ0hBCwt7dH37598eSTT6JVq1bw9PSEo6MjGjRogOLiYuTm5iI9PR0XL17E6dOncf78eQgh0KBBA8yZMwcff/wxpFIOIdIVZ2MRERmfYio2AJXWDsXjvr5Oxa5Mfcmzo89zsFrBzv/+9z8899xzuHPnDrp27YrZs2fjhRdeqHQ174pu3ryJDRs2YNWqVcjJycHAgQOxd+9eODk56Vsdi8Rgh4iodtSXh78hyeQCZ1KzkZlfBA/nstarutaiY9RgJy4uDoMGDYIQAl988QWmTp0KiaT6L0BOTg5mzZqFzZs3o1evXjh69Cjs7e2rfT5LwWCHiKj21IeHv6UxWrCTk5OD/v37QwiBH3/8ER06dKhxZRU2btyI999/Hy+//DLWrFljsPOaKwY7RERkyYzejUWmx2CHiIgsmT7PQbNZG4uIiEhf7J6yDAab/vTPf/4ToaGhOndBvfzyy2jVqhV2795tqCoQERHpLCoxHf2WH8WY9acwc0cCxqw/hX7LjyIqMd3UVSMDM1iws2/fPhw7dgwNG+q2Rkj//v1x9epV/Prrr4aqAhERkU4UU8orrgWlWN2bAY95MViwc+nSJQBAjx49dCofEhICADhz5oyhqkBERFQlmVxg8b4kjVmCFdsW70uCrKqFsajeMFiwc+9e2UJoPj4+OpVv2bIlAODGjRuGqgIREVGVzHl1b9LMYMGOXC5X+X9VZDIZACArK8tQVSAiIqqSpS3wSQYMdho3bgwAOHHihE7ljx8/DgBwdXU1VBWIiIiqZGkLfJIBg51u3bpBCIGFCxeiuLi40rIPHjzAhx9+CIlEgu7duxuqCkRERFWq7gKfMrnAyZQs7Em4iZMpWRzTU48YLNgZNWoUACA+Ph5DhgzB5cuXNZa7fPkyhg4dioSEBADAhAkTDFUFIiKiKlVndW9OU6/fDJZBWSaTISAgAElJSWUnlkjQu3dvBAYGwtXVFfn5+Th79ixOnDgBuVwOIQRCQ0Nx5MgRQ1ze4jCDMhFRzei6wKdimnrFh6U5r3xeH5hsuYhLly4hJCQEt27dKju5hgVCFZcLCgpCVFQUH9TVxGCHiKjmymdQbuxoC0iAu/eLldmUAaDf8qNaZ29JAHi52iF2bigzL9cyky0X0bZtW5w9exZz5szB999/j9LSUrUyjRo1wjvvvIP3338fDRpwtQoiIjIdK6kEvVs3QlRiOmb/eE6tleeVHr46T1Pv3bqRweolk8twNvMs7hTeQROHJgj0CISV1Mpg57c0RlsI9N69ezh+/DiuXr2K+/fvo2HDhujYsSN69+4Na2trY1zSorBlh4jIMCrrptL1AbnylQCMCNAtz1xVjqQdwbIzy3C78LZym6eDJ+b1nIfBLQYb5BrmoE4sBNqwYUMMHz7cWKcnIiKqMV2yKevCUNPUj6Qdwbsx70JUuHpmYSbejXkXK4JXMOCpBoPNxiIiIqpvqsqmXBVt09SrQyaXYdmZZWqBDgDltuVnlkMml9X4WpaGwQ4REVksfbIk6zpNvbrOZp5V6bqqSEAgozADZzPP1vhalsbgwY4QAnv27MGECRPQuXNneHl5wcPDAx06dMCLL76ItWvXIi8vz9CXJSIi0puu3U/vDG4HL1fVsl6udgaddn6n8I5By9FjBh2zc/XqVYwePRrx8fHKbYrxz1lZWbh06RL++9//Yv78+fj888/x+uuvG/LyREREelFkU87ILdI4Rkcxtfz/Qtvg/0LbKKepK6amG3K6eROHJgYtR48ZrGUnJycHwcHBiI+PhxACQghYWVnB09MTPj4+cHJyUm7Py8vD9OnTsWDBAkNdnoiISG/6ZFNWTFMfEeCD3q0bGTyvTqBHIDwdPCHRspCFBBJ4OXgh0CPQoNe1BAYLdpYvX45r165BCIFXX30Vp06dQmFhIdLT03H9+nXk5uYiPT0dmzZtQseOHSGEwLJlyxATE2OoKhAREektrJM31o4LNHo3VVWspFaY13MeAKgFPIqf5/acy3w71WCwPDtPPPEE/v77b8yaNQtffvllpWWLi4vx1FNP4bfffsPIkSOxa9cuQ1TBojDPDhGRYZXPpmyMbipdacqz4+Xghbk953LaeTkmWS7CwcEBxcXFSEtLQ7Nmzaosf/r0afTu3Ruenp5IT+dCavpisENEZL6YQblqJkkq6OTkhOLiYjRpotvAqYCAAABlY32IiIjqstpu9bGSWqGHVw+jnd/SGCzY8ff3x/Hjx5GUlIQnn3yyyvKKIKdp06aGqgIREZHB6bo6OtVdBhugPHnyZAgh8O233+pU/ueffwYAPPPMM4aqAhERkUEp1s2qmGU5I7cI07eeRVRi9YdhyOQCJ1OysCfhJk6mZEEmN8pSlQQDLwQ6atQo7N27F7/88gtCQ0O1lisqKoK/vz+ysrLw119/sXWnGjhmh4jIuGRygX7Lj2pdTkKRgyd2bqjeXVpsLao5k4zZ+e233/DWW28hPT0dzz//PP7973/Dw8NDY9lvv/0WaWlpWLBgAS5fvozLly+rlRkwYIChqkZERKS3qtbNEgDSc4twJjUbvVs30vm82lZZT3/UWlSb090thcFadqRSKSSSsshWCKH8vlqVkkhQWlpqiGqZLbbsEBEZ156Em5i5I6HKcitfCcCIAB+dzllVaxFQ1sJTndYiS6PPc9Cga2MpMiSX/766X0RERKak67pZupYDdFtlXdFaRIZjsG6s7777zlCnIiIiMonyU8wbO9rCy8UOt/MqXzerp5+7zufPyH1g0HKkG4MFO+Hh4YY6FRERUa3TNGjYzcEaAmWBTfmAp+K6WbrKLnho0HKkG4Ouek5ERFQfaRs0nFtYAgBwdbBGzqPvgbIWnerMnHJ3sjVoOdKNyYOdhIQEZTZlQ7pz5w4uX76M3r17G/zcRERkPmRygcX7kjR2VSladewaSLFtSi/cvV9cowzKXi66je/RtRzpxmADlO/du6f3Mb/++iuCg4MNcv3MzEzs3LkTb775Jjp27AgPDw/Mnz+/0mMkEkmlX3fv3q1WXbZu3YqgoCA4OTmhUaNGGDZsGE6dOlWtcxERkXHpMsU8I68YUokEIwJ80Lt1o2rPlOrp5w5v18oDGW89xwFR1QzWshMUFIT9+/ejTZs2OpXfsWMHJk6ciJKSkqoL6+D9999HZGQkpFIpGjdurPNxTk5OGDt2rMZ99vb2etfjzTffxNq1a9G4cWOMGDEC9+/fx6FDh3Dw4EFs3rwZr776qt7nJCIi48nM1xzoSCFHT+lFeCAHmXBDZl5nALrn09HESipBxDB/TN96FoBhxgFR1QyaZ6dRo0b46aefqkwI+NVXX2H27NmQy+WwtrZGcXFxja+/e/duSKVS9O/fH3v27MGkSZMwcOBAxMTEaD1GIpGgRYsWuHr1ao2vDwCbNm3CpEmT0LVrV0RHR6Nhw4YAgPPnz6Nfv34oLi7GhQsX0K5duxpfi3l2iIgM42RKFsasV219f0p6BhHWm9FU8ngKeLGDF2yf+wfgP7zG12QG5ZozSQZlPz8/pKamYujQofjmm2+0zs56//338eWXX0IIAUdHR/z4448Guf7IkSMNcp7qKi0txYIFCwAAa9asUQY6ANClSxfMmzcPCxYswKJFi7Bjxw5TVZOIiCro6ecOHxdr+N4/Bw/koIUkA+80UH822RTeBnZOAEZvrnHAE9bJG0P8vWp1JXVLZrBg58yZMxg+fDhOnjyJ1157DX///Tc+/fRT5X6ZTIZJkyZh27ZtEEKgcePG2L9/P3r0MI8l7I8ePYpbt27B19cXffv2Vdv/yiuvYMGCBdi9ezfy8vLYGkNEVEdYXdyHI1ZzYG+TodwmBFBxIQCJYrhy1Dygw7OA1Kpm15VK9FpmgqrPYAOUGzVqhOjoaLz88ssQQmDZsmV4+eWXUVxcjIKCAjz77LPKQKdly5b4/fffzSbQAYDjx48DALp166Zxf6tWreDm5obi4mLExcXVZtWIiEibpL3Azgmwf5Chsln7ikcCyLsJpJ0wetXIcAw69dzGxgbff/892rRpg08//RQ//vgjrl69Crlcjvj4eABlXTpRUVHw8vIy5KWrLS0tDQ4ODvDy8kKPHj0wbtw4DBs2TO/zJCcnAwCaNWumtUyzZs2Qk5OD5ORkravCFxcX6zSGKS8vT+86EhFROXIZEDUX0DjpvAr3bxu8OmQ8Rsmz8/HHH6Nt27Z4/fXXVVoxgoODsXv37jrThTNu3DjY2toiOzsbf/zxB3bu3ImdO3fi6aefxo8//ggHBwedz5WbmwugbHaXNo6OjiplNVm6dCkWL16s83WJiKia0k4Aebeqd6yTp2HrQkZltKSCEyZMQIsWLTBq1Cjcu3cPL730ErZs2QIbGxtjXVJvW7ZsUfk5Ojoa48ePx8GDBzF16lRs27ZN73NWNrlNl4lv8+fPx7vvvltluby8PPj6+upVNyIiKqdarTMSwKUp0KKPwatDxqN3sPPbb7/pXFYikWDZsmXYvn07pk+frjWxXlVT1WtLSEgItm3bhuDgYGzfvh1Lly5F8+bNdTrWzc0NAFBQUKC1TH5+vkpZTWxtbWFryzThRERGp3frzKOBPGHLajw4mWqX3sFOcHAwJNpHbmk1aNAgjdslEglKS0v1Pp+xDBw4EG5ubsjJycH//vc/nYOdDh06AACuX7+utYxin6IsEREZTvkVy3Wayt2iT1krTV46dBq349K0LNAxQJ4dql3V6sYyUB7COksmkwEAioq0pw+vSNE6pRiIXdHFixdx//592NnZaZ2xRUSkD70f7masWkn6pFZA2PKy3Dka1zUXQPAHQKPWZa1ALfqwRaee0jvY+e6774xRjzrj3Llzyu6m9u3b63xccHAwmjVrhhs3buDEiRPo00e1P3f79u0AgBEjRsDZ2dlwFSYii8QMvI9pW7E8I7cI07eexdpxgdpfE//hZUkCo+aqDlZmK45ZMdhyEXWJYtmGypaLKCwshJWVlcr4mIyMDIwYMQJnzpxBYGAg4uLi1LrsBg0ahJs3b6Jnz57YvHmzyr7IyEhMnDgRgYGBOHr0KFxdXQGUtfYMGDAApaWlXC6CiGpM28Nd8W5V6cPdzMjkAv2WH9W6kKcEgJerHWLnhlbe6iWXlc3Oun9bp1YctqqZnkmWizC1yMhInDx5EsDjnDd///033njjDWWZdevWKb8/c+YMhg0bhqCgIPj4+ODOnTuIjY1FXl4eWrRogf/85z8axyalpKQgLS1NY56g8PBwnD59GmvXrkX79u0xZMgQ5Obm4tChQ5DL5di8ebNBAh0islwyucDifUkaR5g8yu+LxfuSMMTfyyIevrqsWJ6eW4QzqdmVZyuWWgF+/XW6JlvV6h+zCXaio6MRGRmpsi09PR3ffPON8ufywU7jxo3Rrl07xMXFITo6Gk5OTujYsSNGjBiBN998s9pdTV9//TWCgoKwevVq7Nq1C3Z2dggLC8OCBQvQs2fP6t0cEdEjBnu4mwltK5ZXt1xVatRlRiajV7CTk5ODbt26obS0FPv27UOXLl0MVpFVq1bhvffew8SJE7F+/Xq9j9+0aRM2bdqkc/lOnTppHUxcGV1WSJ8wYQImTJig97mJiKpS2w/3us7D2c6g5SrDVrX6S6+1sdzc3LBr1y7k5+ejd+/eWL16dY1nZmVlZeGVV17BO++8g969e+Nf//pXjc5HRGTOavPhXh/09HOHt6sdtIUWEpR1MfX0c9fpfDK5wMmULOxJuImTKVmQyR8/4/RpVaO6Re+FQLt27Ypff/0VLi4umDVrFrp27YotW7boNU0bAG7cuIFFixahTZs22LlzJwYNGoSoqCgm1CMiqoShH+71nZVUgohh/gCg9poofo4Y5q9TS0tUYjr6LT+KMetPYeaOBIxZfwr9lh9FVGI6ALaq1WfVno11+/ZtjB8/HkeOHIFEIoGtrS369euHJ598Eq1bt4aXlxccHR3RoEEDFBcXIzc3F7du3cJff/2F06dP4/z58wAAKysrzJ8/HxEREZBKDbYIu9njbCwiy6UYNwKoZ4YBLGs2lkJNBw3rMsPN1d4GY9ZrXgmgvO+nBlnEeClT0+c5WOOp5z/99BMWLVqEv/76q+yEOmRXFkJAIpFg2LBhWLZsGTMKVwODHSLLxhlB6qo7HVzX6evH5oRg4D+ikZFbpHHcjs7T3MkgajXYUTh8+DB27NiBAwcO4PZtzYurSSQSdO7cGc899xwmTpyINm3aGOLSFonBDhEx14thnEzJ0rnFJvfBQ7aq1REmybMzZMgQDBkyBABw69YtXLx4EdnZ2SguLoaTkxO8vLzQsWNHODk5GeqSREQWzUoqYXeJAegzFmdEgA/WjgtUa1Xzqs+tanomVKyPjJJnp2nTpmjatKkxTk1ERGRQ+s5wC+vkjSH+XubRqpa0V8tSGcvNaqkMs0kqSEREVB2KGW5VjcUpP8PNLFrVkvY+WgS1wl3npZdtH73ZbAIeTn8iIiKLZsjp6/WGXFbWoqM1RSKAqHll5cwAgx0iIrJ4YZ28sXZcILxcVbu0vFztzHPQcdoJ1a4rNQLIu1lWzgywG4uIiAhmNhanKvc1z5qudrk6jsEOERHRI2YxFkcXTp6GLVfHMdghIiIl5u6xEC36lM26ykuH5nE7krL9LfrUds2MgsEOEREBsLyszBYd2EmtyqaX75yAsmHYGlIkhi0zm3w7BsugTLWLGZSJyJB0WRvKnAIeSwvstNKYZ8enLNCp49POTbJcBNUuBjtEZCi6rg1lLms+WVpgV6V6mkFZn+cgp54TEVm4M6nZWgMdoKyDIz23CGdSs2uvUkYikwss3pdUWXYZLN6XBJncgtoBpFaAX3+g84tl/68HgY6+jBrsHDp0CO+++y6SkpKMeRkiIqoBfdaGqu8sKbCjx4wa7OzcuRMrV66EtbW12r7k5GSEh4dj4MCBmDx5MuLj441ZFSIi0kLftaHqM0sK7Ogxo87G+t///ofu3bujbdu2Kttv376N3r17Izc3F0IIHD9+HFu2bEFkZCTGjBljzCoREVEF1Vkbqr6ypMCOHjNqy86tW7fQqVMnte1ff/01cnJy8OSTT2LLli1Yvnw5XFxcMHXqVFy7ds2YVSIiogosaW0oRWCn7U4kKJuVZQ6BHT1m1GAnPz8fnp7q2Rd3794NiUSC//znPxg7dizmzJmDPXv2oLCwEGvWrDFmlYiISANzWhtKJhc4mZKFPQk3cTIlS2WwsSUFdvSYUaee+/j4YMiQIdi0aZNy271799C4cWN06dIFf/zxh0r5vn37orCwUG07qePUcyIyhvqeaE/X/DnMs1P/6fMcNOqYne7du+PQoUN4+PAhbGxsAACHDx+GEAKhoaFq5QMCArB161ZjVomIiCpRn9eG0pY/JyO3CNO3nlVpobKoRT/JuN1Yr732Gm7fvo13330XJSUlkMlkWLt2LSQSCYYOHapWvmHDhnj48KExq0RERGaoOvlzFIHdiAAf9G7diIGOGTNqsDNixAiMGjUKX3/9NRo1aoQmTZrgt99+g7e3NwYPHqxW/tatW/Dw8DBmlYiIyAwxfw5VxugZlLdv345Zs2YBAHJycuDm5oaNGzfCyko9Q+Px48fh6+tr7CoREZGZ0TUvzuGkDCPXhOoiowc7NjY2WLFiBbKysnDjxg3cuXNHYxfW8ePHkZKSguDgYGNXiYiIzIyueXE2/n4VUYnpRq4N1TV6BTtjx47F559/jl9++QWZmZl6Xcja2hpNmzaFVKr5klevXkW/fv0wbNgwvc5LRESkyJ9TFQkscO0r0m/quVQqhUTyeACXp6cnAgIC0LVrVwQEBCAgIADt2rVTKUPGwannRESqohLT8cbWszqV/X5qUL2ddUZljDb1vF27drh8+TLkcjkAICMjA4cOHcKhQ4eUZezt7dG5c2eVIKhLly5wcHCoxq0QERHpJqyTNyb3bYl//361yrJc+8qy6BXsXLx4EUVFRbhw4QLOnTun/Dp//jzy8vIAAIWFhThz5gzOnDmjPE4ikaBNmzZqrUDe3kzcREREhjPY30unYIdrX1kWg2VQTklJwVtvvYVffvlF+8UqdG81btwYTz75JKKiogxRBYvCbiwiInUyuUC/5UerXNQ0dm4o8+rUc/o8Bw0yG0sul+Pdd9/F4cOH0bp1a6xbtw6///47Lly4gMOHD+OTTz5Bz549IYRQ+bpz5w4OHz5siCoQERFx7SvSyCAtOytXrsQ777yDnj174tixY7C1tdVY7n//+x9mzJiB06dPw8nJCWPGjMHFixdx7NixmlbB4rBlh4hIO659Zf70eQ4aJNjp0qUL/vzzT+zatQsjRoyotKxMJsPkyZOxefNmhIaG4siRIzW9vEVisENEVLn6vqgpVa7WFwJNSUkBADRv3rzKslZWVtiwYQPOnTuH6OhofP/99xgzZowhqkFERKRUnxc1JcMyyJgdJycnAMD169d1Kt+gQQO8/fbbEELg+++/N0QViIiIiDQySLDTo0cPAGXrYOmqS5cuAICzZ3VLAEVERERUHQYJdiZOnAghBH744Qf88MMPOh2Tn58PAMjKyjJEFYiIiIg0Mkiw8+KLL2Lw4MEQQuDVV1/Fp59+ipKSkkqP+e677wCAmZWJiIjIqAy26vkPP/yAwMBAyGQyLFq0CO3atcPSpUvx559/qpRLSUlBeHg4tm7dColEgj59+hiqCkRERERqDJZBGQCKi4vx1ltvYePGjWUnf5Qx2d7eHo0bN0Z2djYKCgoAAEIIWFtb49ixYwgKCjJUFZTu3LmDy5cvo3fv3gY/d13AqedERGTJaj2DsoKtrS02bNiA48ePY9CgQcpMyYWFhbh27Rru37+v3Obm5oYdO3YYLNDJzMzEzp078eabb6Jjx47w8PDA/PnztZa/ffs2Vq9ejUGDBqFly5awtbWFp6cnRo4cidjY2GrV4erVq5BIJFq/FLPWiIiIqPYYJM9ORX379sXhw4eRnp6OqKgonDt3DhkZGXjw4AE8PDwQFBSEl156yaAtEu+//z4iIyMhlUrRuHHjKsuHh4fj0KFD8PHxQVBQEOzs7BAfH489e/Zg3759WLNmDd54441q1cXb2xvDhw9X225nx4XniIiIaptBu7FMaffu3ZBKpejfvz/27NmDSZMmYeDAgYiJidFYfvHixQgKCsLQoUOV3W1yuRwLFy7E0qVLYWNjg0uXLumUKFHh6tWr8PPzq/S6hsJuLCIiy8Xs0CbIoFwXjBw5Uq/yERERatukUik+/vhjbNiwAXfu3MH+/fsxffp0A9WQiMiy8IFsHFz3S39mE+wYipWVFVq2bIk7d+4gOzvb1NUhIqqX+EA2jqjEdEzfehYVu2QycoswfetZrB0XyNdXA4MOUDYHpaWluHLlCgCgRYsWJq4NEVH9o3gglw90gMcP5KjEdBPVrH6TyQUW70tSC3QAKLct3pcEmdwsRqcYFIOdCnbs2IGsrCw4OTlh2LBh1TrHsWPH4OTkhPbt2yM8PBy//fabzscWFxcjLy9Ppy8iorqGD2TjOZOarRZAlicApOcW4UwqeyUqYrBTTlpaGmbNmgUA+Oqrr+Dq6qrX8Y6OjpgwYQJee+01hISEID8/H5s3b8bAgQMxefJkyGSyKs+xdOlSuLq6Vvnl6+tbnVskIjIqPpCNJzNf++tanXKWhGN2Hrl27RpCQ0ORlZWFpUuXYvLkyXqfo0mTJoiMjFT+LITArl278Nprr2Hjxo3w8vLCp59+Wuk55s+fj3fffbfKa+Xl5THgIaI6hw9k4/Fw1i19ia7lLAmDHQAJCQl45plncOfOHWzYsKFagY4mEokEL7zwAu7fv4+JEydi5cqV+PDDDyvNt2NrawtbW1uDXN/ScSYIUe3jA9l4evq5w9vVDhm5RRq7CSUAvFzL3utIlcUHOydPnsTTTz8NmUyGAwcOYMiQIQa/hmJafEFBAf78809069bN4NcgVZwJQmQafCAbj5VUgohh/pi+9SwkgMrrq/gYFzHMnx/qNLDoMTvnzp1DWFgYHj58iF9++cUogQ5QNsNLoaiITbfGxpkgRKajeCADjx/ACnwg11xYJ2+sHRcIL1fVljEvVztOO6+ExbbsZGdnY9iwYcjLy0NkZKRRFwxVrLUlkUjQtm1bo12Hqp4JIkHZTJAh/l58syUyEsUDuWLrqhdbVw0irJM3hvh7sZteDxYb7MyaNQvXr19H9+7dMWHCBJ2PGzRoEG7evImePXti8+bNKvvy8vLg6OgIKysr5bbLly8rBxyPHDkSHh4ehrkB0kifmSC9WzeqvYoRWRg+kI3LSirhe5gezCbYiYyMxMmTJwEAycnJAIC///5bZTHPdevWAShbIX3r1q0AyrqYtC34GR4ertbik5KSgrS0NHh5eamV37VrF9577z306tULnp6euHnzJo4fP46ioiIEBATgm2++qfmNUqU4E4So7rDEBzInRtRNZhPsREdHq0z7BoD09HSVAEMR7BQWFkKx/mlCQgISEhI0njMoKEiv7i1fX1/4+PggNjYWhYWFcHNzU67wPmXKFNjY2Oh5V6QvzgQhIlPhxIi6y2xWPbc0XPVcM5lcoN/yo1XOBImdG8pPW0RkMNrWrFK8y3DwsOHp8xy06NlYZH44E4SIahuXyKj7GOyQ2eHUTCIyJJlc4GRKFvYk3MTJlCy1oIVLZNR9ZjNmh6g8zgQhIkPQZRwOJ0bUfQx2yGxZ4kwQIjIcbeNw0nOL8MbWs5jctyUG+3uhsZNuS/xwYoTpMNghIiKqoLJxOAr//v0q/v37VXi52MLNwRq5hSVcIqOOYrBDRGRAzLNiHqoah1Pe7bxiZZDDNavqJgY7REQGwjwr5kOf8TWKpWhcHaxh18AKGXlcIqOuYbBDRGQA2sZ3KBag5UzA+kXf8TUCQE5hCbZNDoRUKmHLXh3DYIeIqIa4AK356ennDm9XO60JSrW5W1CMEQE+RqsXVQ/z7BAR1RDzrJifyhKUVoYzruomBjtERDXEPCv1T1WJAgHtCUo1kaBsfBZnXNVN7MYiIqohLkBbv+gzkLx8gtLDSRnY+PtVzriqBplchrOZZ3Gn8A6aODRBoEcgrKRWtXZ9BjtERDVU1fgO5lmpO6ozkFyRoLR360bo6eeuFihxxlXljqQdwbIzy3C78LZym6eDJ+b1nIfBLQbXSh246nk9xVXPieoWxUMU0Pypn7OxTE8mF+i3/KjW8VWKoDR2bmilLTTMpaS7I2lH8G7MuxAVwkvJo7+MFcErqh3wcNVzIqJaxgVo6z5DDSRXtPSMCPBB79aNGOhoIZPLsOzMMrVAB4By2/IzyyGTy4xeF3ZjEREZCBegrds4kLx2nc08q9J1VZGAQEZhBs5mnkUPrx5GrQuDHSIiA+ICtHUXB5LXrjuFdwxariYY7BARkV7q65gVYw4kr6+viTE1cWhi0HI1wWCHiIh0Vp/X/1IkCpy+9axBp4/X59fEmAI9AuHp4InMwkyN43YkkMDTwROBHoFGrwsHKBMRkU4UM84qDvJVTNuOSkw3Uc10Z+iB5ObwmhiLldQK83rOA/B49pWC4ue5PefWSr4dTj2vpzj1nIhqk6GmbdcVhuh2MrfXxFg05dnxcvDC3J5za5RnR5/nILuxiIioSvpM264PA7QNMZDc3F4TYxncYjBCfEOYQZmIiOo2TttWx9dEd1ZSK6NPL68Mx+wQEVGVOG1bHV+T+oPBDhERVUkxbVvbyBNLXPWbr0n9wWCHiIiqpJi2DUDt4W6pq37zNak/GOwQEZFOuP6XOr4m9QOnntdTnHpORKbCbMHq6uNrUh/rXB6nnhMRkdFw/S919e01sbSsz+zGIiIisiCWmPWZwQ4REZGFkMkFFu9L0rgQqmLb4n1JkMnNa4QLgx0iIiILoU/WZ3PCYIeIiMhCWGrWZwY7REREFsJSsz4z2CEiIrIQlpr1mcEOERGRhbDUrM8MdoiIiCyIJWZ9ZlJBIiIiCxPWyRtD/L3qdQZlfTDYISIiskD1LetzTbAbi4iIiMwagx0iIiIyawx2iIiIyKyZbbBz584dnDx50tTVICIiIhMzm2AnMzMTO3fuxJtvvomOHTvCw8MD8+fPr/K4AwcOIDQ0FK6urnB1dUVoaCgOHjxYo7ps3boVQUFBcHJyQqNGjTBs2DCcOnWqRuckIiKi6jGbYOf999/Hyy+/jG+++QZ3797V6Zhly5bh2WefRVxcHJ566ik89dRTiIuLwzPPPIPly5dXqx5vvvkmxo8fj5SUFIwYMQL9+vXD4cOH0a9fP2zfvr1a5yQiIqLqkwghzGId9927d0MqlaJ///7Ys2cPJk2ahIEDByImJkZj+ejoaAwaNAg+Pj74/fff0bx5cwDA9evX0bdvX9y4cQO//vorQkJCdK7Dpk2bMGnSJHTt2hXR0dFo2LAhAOD8+fPo168fiouLceHCBbRr167G95uXlwdXV1fk5ubCxcWlxucjIiKqT/R5DppNy87IkSMxfPhwZYBRlXnz5kEIgWXLlikDHQDw9fXFsmXLIITQqRtMobS0FAsWLAAArFmzRqUeXbp0wbx58/Dw4UMsWrRI53MSERFRzZlNsKOPv//+G2fOnIGNjQ1GjRqltv/555+HjY0NTp8+jb///luncx49ehS3bt2Cr68v+vbtq7b/lVdeAVDWApWXl1ezGyAiIiKdWWSwc/z4cQCAv78/7O3t1fbb29vD379sobTY2Fi9ztmtWzeN+1u1agU3NzcUFxcjLi6uOtUmIiKiarDI5SKSk5MBAM2aNdNaplmzZkhISFCWNdQ5c3JykJycjNDQUI1liouLUVxcXOX12DpERGQichmQdgK4fxtw8gRa9AGkVqauFVXCIoOd3NxcAICTk5PWMo6Ojipla+ucS5cuxeLFi3W6JhER1bKkvUDUXCDv1uNtLk2BsOWA/3DT1YsqZZHdWAqVTUSr7iS1mp5z/vz5yM3NrfLr+vXr1aofERFVU9JeYOcE1UAHAPLSy7Yn7TVNvahKFtmy4+bmBgAoKCjQWiY/P1+lbG2d09bWFra2tjpdk4iIaolcVtaiA00fWgUACRA1D+jwrFqXlkwucCY1G5n5RfBwtkNPP3dYSSW1UWt6xCKDnQ4dOgBApa0j165dUylriHMq9ul6TiIiqiPSTqi36KgQQN7NsnJ+/ZVboxLTsXhfEtJzi5TbvF3tEDHMH2GdvI1YYSrPIrux+vcv+4f4119/4cGDB2r779+/rxxwrGkauSYDBgwAAMTHx2vcf/HiRdy/fx92dnZaZ2wREVEddf+23uWiEtMxfetZlUAHADJyizB961lEJaYbsoZUCYsMdtq0aYPevXvj4cOH+O9//6u2f+fOnSgtLUWvXr3Qtm1bnc4ZHByMZs2a4caNGzhx4oTafsVSESNGjICzs3PNboCIiJRkcoGTKVnYk3ATJ1OyIJMbYWEAJ0+9ysnkAov3JWnt9AKAxfuSjFNXUmORwQ4AfPbZZ5BIJJg/fz5u3ryp3J6amooFCxZAIpFg6dKlascNGjQIHTp0wIQJE1S2W1lZ4ZNPPgEAvP322yozruLj4/Hll1/CxsYGS5YsMdIdERFZnqjEdPRbfhRj1p/CzB0JGLP+FPotP2qQVhOVIKq0PYRLUwDaxtpIABefsmnoAM6kZqu16JQnAKTnFuFManaN60lVM5sxO5GRkTh58iSAxzlv/v77b7zxxhvKMuvWrVN+HxwcjKVLl2LevHno2LEjnn76aZSWluLgwYMoKCjA8uXLNa6LlZKSgrS0NHh5eantCw8Px+nTp7F27Vq0b98eQ4YMQW5uLg4dOgS5XI7NmzcbZF0sIiJ63E1UsW1E0U20dlxgtcfFaBpr84rTOCzFPyCBBKoDlR8FQGHLlIOTM/O1Bzrl6VqOasZsgp3o6GhERkaqbEtPT8c333yj/Ll8sAMAc+fORadOnfD555/j559/hpWVFYKCgjBnzhw89dRT1arH119/jaCgIKxevRq7du2CnZ0dwsLCsGDBAvTs2bNa5yQiIlVVdRNJUNZNNMTfS++ZT1GJ6Xhj61m17f+5H4Ac6Uz803UH7B9kPN7h0rQs0CmXZ8fD2U6na+lajmrGbFY9tzRc9ZyILNnJlCyMWX+qynLfTw1C79aNdD6vTC7Q7ZPDyCks0bhfAqCpizV+e8UOVgWZWjMoy+QC/ZYfRUZukcaATALAy9UOsXNDOQ29mixy1XMiIqp7jDV42FjdRP86ellroAOUtRrdzCvBGdER6Pxi2TRzDUtFWEkliBjmrzXQAYCIYf4MdGqJ2XRjERFR3WLMHDPG6CaSyQW++z1Vp7K6BlFuDtZqwZOrgzWWjerMPDu1iC07RERkcMbOMdPTzx3ernaVzY2Ct2tZtmJdnUnNRs4D7a065VUVRCnuX1MrUW4lLUdkHAx2iIjIoGojx4yimwhQnwxe3W4iXVtrHGysKg2iKrt/BU33Xyv5giwUu7GIiMig9Mkxo8/g4YrCOnlj7bhAta4yr2p2lena5SWTC5y6koWgVo00BlPVuX8uK2FcDHaIiMigajPHTFgnbwzx9zLIQps9/dzh7miN7ILKu5mKS+UYu+G01mBE3/s3Zr4gKsNuLCKiR9iNYBi1nWPGSipB79aNMCLAB71ba25t0fU8zwf46Fxe2/gjfe6fy0rUDrbsEBGB3QiGpBg8XFWOGX0GD9eWwf5e+PfvV3Uqqy15oT73X1tdfpaOLTtEZPG4OrVhGWPwsK5q2jpX1SyvijStcaXP/XNZidrBYIcsHrsuLBu7EYxDMXjYy1W1S8fL1c5oY1AMsShoZYFKGTmsHFLQwCUBVg4pAOQA1IMRXe9f1y6vxo62fJ+qAS4XUU9xuQjDYNcFGWvZASojkwuDDB6uirZBvgrvDG6L/wttq/O1Nb03NHBOhK3nPkitc5Xb5CWuKL49DFtemaLx30dV96/LshKuDtawa2CFjDy+T5Wnz3OQwU49xWCn5rS9OSrehjgDwjLsSbiJmTsSqiy38pUAjNBj8CrVHkXAUNnYFwDwcrHDR8N1DxBkcoFTKVl4a/tZ3G/wB+x8tgIAJOXiJSHK3jO+DF6BoS2HKI/TJ8BTvBcB6mupa3tA832Ka2MRVYldF6TA1anrv6oG+Spk5Ok3BstKKkHfto3x2Sh/2HruA6Aa6Ch/lgD/+N/nkMll1epK09bl5eliCzcHa43H8H1KP5yNRRaJMyBIoT7PHLJ0ihaUdccu63VcxdlTVWnSJF2l60qTjMIMrDt9GF/skVUrX46mfEFyITB2w2mt1+T7lO7YskMWiTMgSMGUM4eo+sq3oBz7+67Ox2maPVWVO4V3dCr33enzNWotrpgv6O79Yp2uy/epqjHYIYvErgsqzxQzh6j6tKUK0EQKOYKkSRguPYEgaRKkWmZPVaaJQxOdyt3Ls9N6veoEWXyfMhx2Y5FFYtcFVaTPsgO1NcOI1OmyyKbCU9IziLDejKaSxwHGLeGOxSUT4OEcpPM1Az0C4engiczCTAgNV5ZAAhfrxuhddBcf2f5T4/UOyXsC0C/I4vuU4TDYIYuk6LqYvvWs2owHdl1YLkU3QmWYruAxUwR9ug5Gfkp6Bmutv1Lb7oVsrLP5CvKiQAAjNB8slwFpJ4D7twEnT1i16IN5Pefh3Zh3IYFEJeCRPHrHmOraD+OtV2q83lrrrzC9ZBYOyXvq1QrD9ynD4dTzeopTzw2DDy7SB9MVPGaqvx1dUgVIIUes7Qx4IRua4gABCSQuTYFZFwCplerOpL1A1Fwg79bjbS5NgbDlOOLogGVnluF24W3lLi8HL8ztMQeDds2EyLulcWyIXAAZaISXbNfht3lD9A5O+D6lGfPsWAAGO4bDLgnSRVW5XBRdCrFzQ83+348pgz5dkkAGSZOww+aTqk8W/jPg1x9A2e/38rHtaHfsLZSFQ+U9+mn0Zsg6PIuzmWdxp/AOmjg0QaBHIKzSTgCRz1V5uTMDItEzdGTV9dKA71Pq9HkOshuLLJ62rgu+uVB5TFdQpqocVZoWxjSkqsaxAIAHcnQ72f2yFpqoxHR8vPcCfiheCAGhoTXo0Z1FzYNVh2fRw6uH6u7kA7rVvUmpbvXSQJcuVtKOwQ6RBmw2poqYrqCMqYO+ysaxKGTCTadzyRw9cPhRK1UvaRKa2lQ2U0oAeTfLxvI8ag0CUDa+5/x/dLren3n26CAXHPRuAgx2iCrQ1kSvS2IwMl+cBlymLgR9ilQBFT+QONlaQQjgzMMOuCXctY7ZUYyhSS1pj8X7EiGgf2uQIjiRXfkN/QqzqjzsrnDBsH1yeP52VO1DEz9cGR/z7BCVw2UkSBtF94m2z9oSlD2gzHkasEwucDdft0R3xg76wjp5I3ZuKL6fGoSVrwTgncHtUFAsQ8FDGeSQYnHJBABlgU15ip8Xl4zHyav3lAGGrq1BcPJUSWi4MzpOp8N2l/aFHFLlhybF8hHacgZVLEc1w2CHqBx9mujJslh6pmXFA/7j/X9VWq42gz7FOJbnujTFjv9dU/mQckjeE9NLZiEDqvXIQCPlNPDyv8kz8rLWoEo/x7j4IOq+n0pwomuQdER0A6D6oelhqZwfrmoJu7GIyqkLTfRUd2nrPvEy8y4HbV27FZkq6NP2IeWQvCcOF3dHT+lFeCAHmXDDGXkHCEjh7WqH3q0b4V/RlyEVcnS8ewU7HgbjdaefYd/4Iays1K8ja9YTH1cIThRBUlVdZmfkHZTbFB+atpy8ykHvtYTBDlE5HJdBVdEn07I50CdjcXWDvpoOzq3sw4ccUpyS+6ttjxjmj6BWjfBszkW8fOoHNCkqW+jzOhqjgb0MnoG5cPFVPa9V0n+xTxzGfOkUZUZkRZfZWuuvIBdQCXjKd5nJNXSkpGUX1vj+SDcMdggAZwIoMD076cKSpgHrmrH4w2efwMS+fiZJmKfvh493BrdFWCdv5P3yC96K2aC2v/SBFDd/bwj0vacW8DTEfZWMyMDjLrMI681oisdd3BlohMUl45XlKmrh7qBTffnhquYY7BBnApTD9OxEqjLydGtVcHeyrVagU9XMxyH+Xjh1JQsnU7IACPRu1RhBrRupXEvxIUWXoAwAWjZ2hJDJcPuzpVoGnJf99d/+wwXOPkWQlGuUkUgAIYAI6y04XNxd2WKjrctMU4uO4kPT+N4tsSE2lR+uagEHKFs4zgRQxxWwiR7Lvq/b7CtdyynoMvNx3q4L6PbJYYzdcBr/ir6Mf0WnYOy/T6PL4kM4cP7xcg7lB4/rwsPZDoVx8SjNyKiklASlhQ1QeMdGbY9UAjSVZKGn9KLKdkWX2V55H5yS+2sNdICyD002DaQWPei9NrFlx4KZKhNqfegys7RxGUTauDuqP+xrUk5Bl5mPOYUlGvcVFMvw5vY/MO1GDuY/UxYshHXyxtevPon/+/4PrTOqyreU3P+r8iUnFEqLNIxUfkTn3DzlVBzXZKmD3msbgx0LZopMqPWpy8ySxmUQaePlam/QcgqGGHT7zW+p6NqsIZ7pUvbe8UyXpvgXJHhz+1m1shVbSho0aaLTNRrYybTuKz/tXCpRz+lTnpuDNdaMCVTrggP44ao2MNixYLU9zZqZiY2jPrSUUe0wxr8FXcbDVCevjqEG3X64JxFPdXrc+vxMF2+sk1bdUuLQvRsaNG6I0rvZUO9EAgCBBg4yODR5qGGfBMKlKWYOC8eYghJ4ONvhXsFDvPUoyNI01m/ZqM7o27ax1vvghyvjYrBjwWpzmrUhu8z4cH+sPrWUkXEZ699C+UH72gbRVmdciS4Leuoiq+ChWuuzLi0lEisreH64CDdnvoPH70IKZTXyfDJPZXDyoyPL/hu2DL3beqjsWatDkEWmIRFCMDVjPaTP0vbayOQC/ZYfrXImQOzc0BoHEydTsjBmfdV95N9PDar00w0f7o9paylT/KbYUmY5auPfgjH+9hT1BjQv6Kmrf47uiucDm1Xr2LyNn+H26k0offB4bE4Dh1J4PplXNu3cxhF4WPD4ABcfIGwZ4D9c4/n4Yaz26PMcZMuOBavNadaG6DJjN9hjphpcTnVPbf1bMMa4Eq2Dc11sUVQq1zpAuaLsAk1dTbpxee0DOPfwR2HkfJRm3UMDu7KuK4nbo6Cmw7NlK53fvw04eQIt+gBS7YOW2R1VNzHYsXC1NROgpl1mfLirMsXgcoCfWuui2vy3YIwHubYg6nBSBt7Yqj7QWBN3J9sa1UHSeSQcPx+mPajx61+j85PpMdihWpkJUNPMxKZ6uNdVpljDq7JuDM4kMR1zWM9NUxAV1skb7wxui38euVTl8V4uBhjsLLViUGPGGOwQAOM3vda0y8wc3tANqbbX8KqsC/GNrWfh5mCt0uVgqeOoTMGc13P7v9C2+P7M9UqzOCtmgilaHTNyHyC74CHcnWzh5cLAm8ow2KFaU5MuM3N+Q6+O2lzDS5dMtxXHVljiOCpTMef13KykEnw03F/jIObyH5IOJ2Wova8oMPAmgMtFUC0L6+SN2Lmh+H5qEFa+EoDvpwYhdm5olW9Eijd0bZ/PJKhero/6qnx6fGOnmdd1IcjyFA+lxfuSIKss0xrVWG3+W9BGJhc4mZKFPQk3cTIly6C/86qWbwGgcckbhXQLXvqGHuPU83rKEFPP6xtt01Qteap1bUzF35NwEzN3JFT7+KrSCZBhmCotQ21dV9PgeADot/xolcG4IdNoUN3BqedklriGjLraGFxe065BSxlHZWqmWHKgNtNBaBpXeDIlS6dWR0ubwEDqLDrYmThxIiIjI6ssFx4ejk2bNlVZ7urVq/Dz89O639HREffv39enilQB15BRZ+zB5TXNdGsp46jqgtrM8VIX0kHoG0gz8LZcFh3shISEwM5O+xvx6dOnkZCQoHc3kbe3N4YPV8+uWdm1SHdM2lW7KptJV5n6PDCWqlYX0kHoG0gz8LZcFh3shIeHIzw8XOO+/Px8tG/fHq6urpg3b55e523Xrh3WrVtniCoS1QnauhAbOljjXmGJ0TNw64OJD2tHXUgHocsipQADb7LwYKcyERERSE9Px7p169C0aVNTV4fI5CrLdFtXxlFx7bTaUxfSQVS1SGl5pgi8qe7gbCwNEhMT8eSTT6Jbt244efIkJBLd/kAUY3YGDhyImJgYo9bREmdjUd1VF1pTuDBq7arNhYSroinIVWCwa744G6uG3nzzTcjlcqxZs0bnQIfIkpl6HFVdGCxraWpzIeGqlG91ZAZl0oTBTgVbtmzB8ePHMXnyZHTr1q1a5zh27BicnJzg4+ODoKAgTJ48GQMGDNDp2OLiYhQXF1dZLi8vr1p1IzJHdWGwrCWqS+kgTB1wU93GbqxycnNz0aFDB+Tn5+PSpUvw9tbvD/XOnTuYPXs2GjRogMzMTMTHxyM9vSxr52uvvYZvv/0WVlZWlZ7jo48+wuLFi/WqM7uxyNLpmvhw5SsBGBHgY/wKWZi60I1JlofdWNW0aNEiZGRk4IMPPtA70AGAJk2aqOTtEUJg165deO2117Bx40Z4eXnh008/rfQc8+fPx7vvvlvltfLy8uDr66t3HYnMUV0YLGvJ2KpCdR1bdh5JTExEQEAAHBwccPXqVbi7G26KYmRkJCZOnAhHR0fcvXvXIPl2OECZ6LG6NFiWiGqHPs9BLgT6yDvvvAOZTIapU6caNNABgJEjRwIACgoK8Oeffxr03ERUNxbDJKK6i8EOgF9//RVHjhyBVCrFzJkzDX7+0tJS5fdFRUxXTmQMVa2OzanHRJaLY3YALF++HAAwdOhQNG/e3ODnj42NBQBIJBK0bdvW4OcnojJcO42INLH4YCc9PR2//vorAODVV1+tsvygQYNw8+ZN9OzZE5s3b1bZl5eXB0dHR5UZV5cvX1YOOB45ciQ8PDwMWHsiqoiDZYmoIosPdg4ePAi5XA6JRIJnnnmmyvIpKSlIS0uDl5eX2r5du3bhvffeQ69eveDp6YmbN2/i+PHjKCoqQkBAAL755htj3AIRERFVwuKDndOnTwMA2rZti0aNavZp0NfXFz4+PoiNjUVhYSHc3NwQFBSEl156CVOmTIGNjY0hqkxERER64NTzeopTz4mIyJJx6jkRERHRIwx2iIiIyKwx2CEiIiKzxmCHiIiIzBqDHSIiIjJrDHaIiIjIrDHYISIiIrPGYIeIiIjMGoMdIiIiMmsMdoiIiMisMdghIiIis8Zgh4iIiMwagx0iIiIyawx2iIiIyKwx2CEiIiKzxmCHiIiIzBqDHSIiIjJrDHaIiIjIrDHYISIiIrPWwNQVICIyOLkMSDsB3L8NOHkCLfoAUitT14qITITBDhGZl6S9QNRcIO/W420uTYGw5YD/cNPVi4hMht1YRGQ+kvYCOyeoBjoAkJdetj1pr2nqRUQmxWCHiMyDXFbWogOhYeejbVHzysoRkUVhsENE5iHthHqLjgoB5N0sK0dEFoXBDhGZh/u3DVuOiMwGgx0iMg9OnoYtR0Rmg8EOEZmHFn3KZl1BoqWABHDxKStHRBaFwQ4RmQepVdn0cgDqAc+jn8OWMd8OkQVisENE5sN/ODB6M+DirbrdpWnZdubZIbJITCpIRObFfzjQ4VlmUCYiJQY7RGR+pFaAX39T14KI6gh2YxEREZFZY7BDREREZo3BDhEREZk1BjtERERk1hjsEBERkVljsENERERmjcEOERERmTUGO0RERGTWGOwQERGRWWOwQ0RERGaNwQ4RERGZNQY7REREZNYY7BAREZFZ46rn9ZQQAgCQl5dn4poQERHVPsXzT/E8rAyDnXoqPz8fAODr62vimhAREZlOfn4+XF1dKy0jEbqERFTnyOVy3Lp1C87OzpBIJLV23by8PPj6+uL69etwcXGptetS9fD3Vf/wd1a/8PdlOkII5Ofno2nTppBKKx+Vw5adekoqlaJZs2Ymu76Liwv/sOsR/r7qH/7O6hf+vkyjqhYdBQ5QJiIiIrPGYIeIiIjMGoMdIiIiMmsMdoiIiMisMdghIiIis8Zgh4iIiMwagx0iIiIyawx2iIiIyKwx2CG92NraIiIiAra2tqauCumAv6/6h7+z+oW/r/qBy0UQERGRWWPLDhEREZk1BjtERERk1hjsEBERkVljsENERERmjcEOERERmTUGO1Sl27dvY/Xq1Rg0aBBatmwJW1tbeHp6YuTIkYiNjTV19agKd+/eRdOmTSGRSLBp0yZTV4e0yMvLw5IlSxAYGAhXV1c4OjqiTZs2ePnll7Fr1y5TV480iI6OxogRI+Dh4QFbW1v4+fnh5ZdfxtmzZ01dNaqAU8+pSmFhYTh06BB8fHwQFBQEOzs7xMfH4+LFi5BKpVizZg3eeOMNU1eTtBg+fDj27dsHAPjuu+8wceJE01aI1Pz5558YOnQobt26BX9/f3Tr1g0PHz7EtWvXcPbsWYSFhWH37t2mriaV89VXX+Gdd96BtbU1hgwZgkaNGuGPP/5AYmIirKyssH37dowePdrU1SQFQVSFjz76SERFRQm5XK7cJpPJxPz58wUAYWNjI9LS0kxYQ9JmzZo1AoCwt7cXAMR3331n6ipRBffu3RNNmzYVUqlUbNy4UW1/YWGhuHjxoglqRtrcuHFDWFtbCxcXF5GYmKiyb+HChQKAaNWqlYlqR5qwG4uqFBERgaeeegoSiUS5TSqV4uOPP0aTJk3w8OFD7N+/34Q1JE2SkpIwe/ZstG7dGtOnTzd1dUiLTz75BLdu3cKMGTMwadIktf329vZo3769CWpG2pw4cQIlJSUIDQ1Fx44dVfa99957AIA7d+6YomqkBYMdqjYrKyu0bNkSAJCdnW3aypCK4uJijBkzBiUlJdi+fTucnZ1NXSXSoKSkBJGRkQCAN99808S1IV25uroCAC5dugRRYSSIIsjp379/rdeLtGOwQ9VWWlqKK1euAABatGhh4tpQeXPnzsX58+exZMkS9OzZ09TVIS3OnTuHu3fvonHjxmjbti1u376Nf/3rX5g1axYWLVqEmJgYU1eRNAgNDUX79u3x559/Yu7cuSoBz8cffwxXV1d8/vnnJqwhVdTA1BWg+mvHjh3IysqCk5MThg0bZurq0CNRUVFYtWoVQkJCMHfuXFNXhypx4cIFAICvry927tyJKVOmID8/X7n/448/xtNPP43//Oc/bJ2rQxo0aIC9e/diyJAh+Mc//oEzZ87gX//6F7799lscO3YMv/zyi1r3FpkWW3aoWtLS0jBr1iwAZbMSFM26ZFqZmZmYOHEiGjZsiC1btkAq5Z94XZaVlQUAuHr1Kl577TXMnj0bV65cwYMHD3Do0CE0b94cBw8e1DiWh0yrXbt2mD9/PgAgMTERnTt3xurVqzFnzhz06NHDxLWjivhOSHq7du0aQkNDkZWVhaVLl2Ly5MmmrhI9MmnSJNy+fRv//ve/4ePjY+rqUBWKiooAAPfu3cPKlSuxaNEi+Pn5wc7ODkOHDsWOHTsAAD/99BPOnz9vyqpSBbNnz8Zbb72F7777DmlpaZg3bx6sra3x9ttvY8CAAbh7966pq0jlMNghvSQkJCAoKAjXrl3Dhg0bMG/ePFNXiR5ZtWoVDhw4gGnTpmHkyJGmrg7pwMnJCQDg4OCgMf9R79690bVrVwDA8ePHa7NqVIkNGzbgyy+/xMKFCzFx4kQ4Ojpi6dKliIuLQ/v27REbG4vQ0FCUlJSYuqr0CIMd0tnJkycRHByM/Px8HDhwgC06dcxXX30FAPjmm28gkUhUvhYvXgygrOVHIpHgo48+Ml1FSal58+YAyoIdKysrjWXatGkDoCzDMtUNn376KQDg9ddfV9nepUsXxMTEwNXVFRcuXFDOtCPT4wBl0sm5c+cQFhaGkpIS/Prrr+jdu7epq0QV9OnTB61atdK4LykpCenp6fD394e3t7fWclS7unfvDqBsSY+8vDy4uLiolVEMWPby8qrVupFm9+7dw9WrVwEAbm5uavu9vLwQGhqK//73v4iPj8eUKVNqt4KkEYMdqlJ2djaGDRuGvLw8REZGMtCpo7Zu3ap138SJExEZGYk5c+ZwuYg6pHnz5ujVqxdOnz6NTZs2YcaMGSr7CwoKcPbsWUgkEgQHB5umkqSifAtcfHw8BgwYoFYmPT0dADhxow5hNxZVadasWbh+/Tq6d++OCRMmmLo6RGZF0cW4ZMkSlUHIpaWlmDVrFu7evYtXX30Vfn5+pqoilePi4oKQkBAAwNtvv41bt26p7F+7di1OnToFqVSKMWPGmKKKpAFbdqhSmZmZyhaD0tJSrQt+hoeHs8WHqBqeeuopLFq0CEuWLEH37t0xZMgQNGzYEKdOnUJKSgp69uyJr7/+2tTVpHK+/fZbhISE4Pz582jfvj0GDBgAd3d3JCQkKBcCXbVqlXJwOZkegx2qVGFhoTI7aEJCAhISEjSWCwoKYrBDVE2LFy9Gz5498c9//hMnTpzAgwcP0KZNGyxduhSzZs2CnZ2dqatI5bRp0wYXLlzAihUrsHfvXsTExKCkpATe3t4YP3483nnnHTz55JOmriaVIxEVF/YgIiIiMiMcs0NERERmjcEOERERmTUGO0RERGTWGOwQERGRWWOwQ0RERGaNwQ4RERGZNQY7REREZNYY7BAREZFZY7BDREREZo3BDhEREZk1BjtERERk1hjsEFG90aFDB0gkEowZM6ZWr3vv3r1avZ4xmdO9EOmKwQ4R1QsPHjzApUuXAABdu3atteueO3cO3bp1U9m2adMmSCQSSCQSxMTE1FpdDKF///44c+aMqatBVKsY7BBRvXDhwgXI5XIAQEBAQK1c848//kBISAgePnxYK9erDUIIDB48GKdPnzZ1VYhqDYMdIqoXzp07p/y+NoKdjIwMPPPMM3jw4AH27dtn9OvVlh9//BFCCDz33HO4efOmqatDVCsY7BBRvaAIdjw9PeHl5WX0602YMAEZGRlYunQpnnzySaNfr7Y88cQT+OKLL3D37l2MHz/e1NUhqhUMdoioXlAEO7XRqvPTTz/h8OHD8Pf3x9tvv23069W2qVOnomvXroiOjsbOnTtNXR0io2OwQ0R1wo8//ohnn30WHh4ecHBwQOfOnfHVV19BJpNBCIHz588DqJ3ByYsWLQIAzJkzB1ZWVka/Xm2TSqWYPXs2gLJ7FUKYuEZExsVgh4hMKicnB0OHDsVLL72EAwcO4M6dO3jw4AESExPxzjvvYOTIkUhJSUFeXh4A47fs/PLLL0hKSoKtrS1Gjx5drXPcvXsXrVu3hkQiQdu2bZGVlaWyX9Nsri1btiA4OBhNmjSBvb09OnTogHnz5lU5Vby0tBRbtmzByJEj4evrC3t7ezg6OqJZs2YICQnBihUrNB73wgsvwN7eHsnJyfjll1+qdZ9E9UUDU1eAiCxXUVERBg8ejPj4eADAiy++iLFjx8LX1xfXrl3D0qVL8fPPP6vMhjJ2sLNjxw4AQL9+/eDg4KD38cXFxRgxYgSuXLmChg0b4ueff0ajRo20li8tLcWoUaPw3//+V2V7cnIyli9fju+//x7R0dFo1aqV2rGXLl3CqFGjkJiYqLavsLAQN2/ehBAC7777rtp+e3t79O3bF0eOHMF//vMfPPXUU3rfK1F9wZYdIjKZ//u//0N8fDykUim+//57/PDDDxg5ciS6deuG559/Hr/99htat26tbHmwt7dHu3btjFqnAwcOAEC1BiULITBx4kScOHEC1tbW+Omnn9C+fftKj1m4cCF2796NqVOnIi4uDoWFhUhLS8OsWbMAANeuXcPo0aMhk8lUjrt9+zZCQkKUgU5gYCAOHDiA3Nxc5Ofn4+LFi9iyZQteeOEFrddW3KPinonMliAiMoHY2FgBQAAQixcv1lpu3bp1ynI9e/Y0ap1u3LihvNbatWu1lvvuu++U5aKjo5XbFy5cqNz+73//W6fjnZycxC+//KKx3Kuvvqost2vXLpV9EyZMUO7r06ePKCws1O9mhRBr165VnuPWrVt6H09UX7Blh4hM4qOPPgIANG/eHHPnztVarnPnzsrvjd2FdfHiReX3+k5v37x5Mz755BMAwNy5c/Haa6/pdNyGDRswZMgQjftmzJih/P6nn35Sfp+dnY3t27cDACQSCTZu3Ah7e3u96guUTeNXKH/vROaGwQ4R1bpbt27hyJEjAMqmQdva2mot6+Liovxe00wsf39/SCQSZGZm1rhe2dnZyu8dHR11Pu7YsWOYOnUqgLKBv0uXLtX52PIBR0WBgYFo0KBsaOX/t3d/IU1FcRzAvzfnQnS4YkpR0B56iJSQWKEPWaKQZBmN7KWgBvZiD1EgEoJFvUjUgwg9JIpi66E/IPkirBERhBn9ASNxERRFChEalq7N+vUg93TNbc5tTXf7fiA47N5zdu4I/HLu75774sUL9fmjR48wOzsLACgrK1v0Vlk0xms0XjuR2TDsEFHaDQwMqPbBgwdjnjs+Pq7af6/sTE9PIxAIYMOGDSgsLEx6Xj9+/FBtPWQsJhAI4NChQwiFQnC5XOjt7YWmaUnPBQCys7NVcfPnz5/V5+/evVPt7du3Jzy+8RqDwWDC4xCtdAw7RJR2+gaBOTk5KC4ujnmu/g4nTdOwbdu2ecdevnyJnz9/JvUH3ygvL0+1jcEnFq/Xqx4P//Xr14JC4mTpgUR/LxgAfPv2TbXz8/MTHtt4jTabLeFxiFY6hh0iSjt9tcbhcCy6CnLv3j0AwObNm+eFEQB4/vw5gORWN4yMdTpfv36Nq8/58+dx+PBhNR+3241wOJyS+QBQ+wutXbtWfbZmzRrVnpqaSnhs4zXGup1GlOkYdogo7WZmZgAsXify9OlTDA0NAYhcnKzvz5OqsFNUVKTC16dPn+Lqs2rVKty4cQN79uwBAPh8Png8npTsSjw+Pq7CjHEFrKioSLX1wJeIsbExAHOrZlu3bk14HKKVjmGHiNJOr6/5/v073rx5E/Gc2dnZeU8jRSpOTvXKjs1mU09/jYyMxN1v9erV6OvrU7fZvF4vGhsbk56PcWfjyspK1S4rK1O1PI8fP0YgEEhofP0JrOLiYt7GIlNj2CGitCstLVXtixcvLjgeDodRX1+PwcFB9dnfKzvBYBCvX79GQUEBNm7cmLK57d+/H8CfWqF45efnY2BgAE6nEwBw9erVqK9qMLp+/bpa6TIKBoO4fPkygLkQdvToUXXMarXizJkzAOZqeTweD6anp5c0X+DPNR44cGDJfYkyynJv9ENE/5/JyUmx2+1qQ7tjx46J3++XoaEh6ezslJKSEgEgubm56pyPHz/OG+PJkycCQPbu3ZvSuQUCAdE0TQDI+/fvI54TbVNBEZHR0VFxOBwCQDRNE6/XG7M/AHE6ndLR0SFjY2MyMzMjg4ODsnv3bnX82rVrC8YIhUJSWlqqzikpKZH+/n6ZnJyUqakpGR4elvb2dqmrq4t4DR8+fBBN00TTNBkdHV36D0WUQRh2iGhZ3L17VywWy7w/+sZ/FRUVcvLkSQEgDodjQX99999z586lfG61tbUCQJqbmyMejxV2ROaCmB7UrFar+Hy+qP2bm5slLy8v6u/Q1NQUdZ4TExNSUVERtS8A2bRpU8S+Fy5cEABSU1MT9+9ClKl4G4uIloXb7cbDhw+xb98+2O12WK1WrF+/HjU1Nejt7YXf71c1JZGKk1Ndr2PU2toKi8WC9vb2efvbxGvnzp24c+cOLBYLQqEQ3G531ELiqqoqjIyMoKGhAU6nE1arFYWFhaitrcX9+/fR2toa9Xvsdjv8fj9u3ryJ6upqOBwOZGdnY926dXC5XGhoaIh4K21iYgJtbW3IysqKOT6RWWgiKXhkgIgozVwuF549e4a3b99GfCN4slpaWnDp0iXU1dXh1q1bKR27u7sbHo8HAPDgwQP1JFe6nDhxAj09PWhqamLYof8CV3aIKOOEw2EMDw/Dbrf/k6ADzO2fU1VVhdu3b6Otre2ffMdy6OrqQk9PD8rLy9W7vIjMLr790ImIVpBXr14hFArBZrOhvr4+4jm7du3C8ePHE/6OrKws9PX1obq6GmfPnsXp06cTHmslaWxsxI4dO9Df3x/3KzGIMh3/pxNRxtHrX758+YLOzs6I52zZsiXp78nNzYXP58OpU6eSHmulOHLkCK5cubKkF50SZTrW7BARpdly1+wQ/W8YdoiIiMjUWKBMREREpsawQ0RERKbGsENERESmxrBDREREpsawQ0RERKbGsENERESmxrBDREREpsawQ0RERKbGsENERESmxrBDREREpvYbmLasB/W0TjAAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for rem_id in np.unique(t['rem_id_L']):\n", + " idxs = np.where(t['rem_id_L']==rem_id)[0]\n", + " plt.scatter(t['rad_L'][idxs], t['rad_S'][idxs], label='lens type '+str(rem_id))\n", + "plt.xlabel('$d_L$ (kpc)')\n", + "plt.ylabel('$d_S$ (kpc)')\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": {} + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:astro-synthpop]", + "language": "python", + "name": "conda-env-astro-synthpop-py" + }, + "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.0" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index a4aeb89..42489ab 100755 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -1,6 +1,26 @@ Change Log ========== +========================= +v2.0.2 (2024-12-24) +========================= + +* Functions to manipulate lightcurves + * Generate BAGLE models and parameters given a table of events +* HDF5 analysis utilities + * Return a table with lists of star systems and their RA, Dec, z position, and system apparent magnitude. This is useful for making stellar density maps, computing microlens event occurrence rates, etc. + * Count number of stars in hdf5 file + * Makes a fraction of black holes single after perform_pop_syn. This is a temporary fix since there is no binary evolution and all the black holes end up in binaries. +* Bug Fixes + * Issues involving masked columns causing masked blends and magnitudes instead of nans or correctly added ones + * Column names of magnitudes of companion table + +========================= +v2.0.1 (2024-03-12) +========================= + +Small utility bug fixes + ========================= v2.0.0 (2024-01-29) ========================= @@ -8,7 +28,7 @@ v2.0.0 (2024-01-29) New Features: ------------- * Multiplicity - * See Abrams et al., in prep for details + * See `Abrams et al. 2025 `_ * See `new example `_ for details of how to call functions to add multiple systems. * run_galaxia() is not modified, but all other function calls can be optionally changed to add multiple systems. diff --git a/docs/source/popsycle.rst b/docs/source/popsycle.rst index 66d6755..878c17d 100755 --- a/docs/source/popsycle.rst +++ b/docs/source/popsycle.rst @@ -73,4 +73,20 @@ popsycle.utils module :undoc-members: :show-inheritance: +popsycle.binary_utils module +--------------------- + +.. automodule:: popsycle.binary_utils + :members: + :undoc-members: + :show-inheritance: + +popsycle.phot_utils module +--------------------- + +.. automodule:: popsycle.phot_utils + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/source/popsycle_docs.rst b/docs/source/popsycle_docs.rst index 0a6878a..17fe5b6 100755 --- a/docs/source/popsycle_docs.rst +++ b/docs/source/popsycle_docs.rst @@ -276,8 +276,32 @@ confusing, so here is a short guide to the basics. .. image:: popsycle_docs_images/media/pipeline_w_multiples.png :align: center +.. _filters: + +==================== +4 Avaliabile Filters +==================== ++-----------------------+-----------------------+-----------------------+ +| **Photometric | **Filters** | **Status** | +| System** | | | ++=======================+=======================+=======================+ +| UBV | J, H, K, U, | Included by Default | +| | I, B, V, R | | ++-----------------------+-----------------------+-----------------------+ +| ztf | g, r, i | Optional via grid | ++-----------------------+-----------------------+-----------------------+ +| rubin | u, g, i, r, z, y | Optional via grid | ++-----------------------+-----------------------+-----------------------+ +| roman | f062, f087, f106, | Optional via grid | +| | f129, f158, f146, | | +| | f184, f213 | | ++-----------------------+-----------------------+-----------------------+ +| sdss | u, g, i, r, z | w/ Galaxia | +| | | - in progress | ++-----------------------+-----------------------+-----------------------+ + ========== -4 Outputs +5 Outputs ========== In addition to the outputs described below, each function produces @@ -399,9 +423,9 @@ magnitude.) | | system absolute | | | | magnitude | | +-----------------------+-----------------------+-----------------------+ -| ztf_g, r, i | ztf photometric | mag | -| (optional) | system g, r, i | | -| | absoltue magnitude | | +| Optional | Additional | mag | +| photsystem_filtname | photometric system | | +| (see :ref:`filters`) | absolute magnitude | | +-----------------------+-----------------------+-----------------------+ | vr | Galactic radial | km/s | | | velocity | | @@ -487,8 +511,8 @@ magnitude.) | | in filters from | | | | SPISEA system | | +-----------------------+-----------------------+-----------------------+ -| m_ztf_g, r, i | System magnitude | mag | -| | in filters from | | +| m_photsystem_filtname | System magnitude | mag | +| (see :ref:`filters`) | in filters from | | | | SPISEA system | | +-----------------------+-----------------------+-----------------------+ | log_a | Log of the system | log(AU) | @@ -540,126 +564,127 @@ magnitude.) Default name: *root*\ \_events.fits -+-----------------------+-----------------------+-----------------------+ -| **Tag name** | **Brief | **Units** | -| | Description** | | -+=======================+=======================+=======================+ -| zams_mass (_L, | ZAMS mass | M⊙ | -| \_S) | | | -+-----------------------+-----------------------+-----------------------+ -| mass (_L, \_S) | Current mass | M⊙ | -+-----------------------+-----------------------+-----------------------+ -| systemMass (_L, | Sum of mass of | M⊙ | -| \_S) | primary and | | -| | companions (if | | -| | existent) | | -+-----------------------+-----------------------+-----------------------+ -| px (_L, \_S) | Heliocentric x | kpc | -| | position | | -+-----------------------+-----------------------+-----------------------+ -| py (_L, \_S) | Heliocentric y | kpc | -| | position | | -+-----------------------+-----------------------+-----------------------+ -| pz (_L, \_S) | Heliocentric z | kpc | -| | position | | -+-----------------------+-----------------------+-----------------------+ -| vx (_L, \_S) | Heliocentric x | km/s | -| | velocity | | -+-----------------------+-----------------------+-----------------------+ -| vy (_L, \_S) | Heliocentric y | km/s | -| | velocity | | -+-----------------------+-----------------------+-----------------------+ -| vz (_L, \_S) | Heliocentric z | km/s | -| | velocity | | -+-----------------------+-----------------------+-----------------------+ -| age (_L, \_S) | Age | log(age/yr) | -+-----------------------+-----------------------+-----------------------+ -| popid (_L, \_S) | Population ID - | N/A | -| | integer indicating | | -| | the population | | -| | type ranging from | | -| | 0 to 9 | | -+-----------------------+-----------------------+-----------------------+ -| exbv (_L, \_S) | Extinction E(B-V) | mag | -| | at the location of | | -| | star given by 3-D | | -| | Schlegel | | -| | extinction maps | | -+-----------------------+-----------------------+-----------------------+ -| glat (_L, \_S) | Galactic latitude | deg | -+-----------------------+-----------------------+-----------------------+ -| glon (_L, \_S) | Galactic longitude | deg | -+-----------------------+-----------------------+-----------------------+ -| mbol (_L, \_S) | Bolometric | log(L/L⊙) | -| | magnitude | | -+-----------------------+-----------------------+-----------------------+ -| grav (_L, \_S) | Surface gravity | log(gravity) | -+-----------------------+-----------------------+-----------------------+ -| teff (_L, \_S) | Effective | Log(T/Kelvin) | -| | temperature | | -+-----------------------+-----------------------+-----------------------+ -| feh (_L, \_S) | Metallicity | [Fe/H] | -+-----------------------+-----------------------+-----------------------+ -| rad (_L, \_S) | Galactic radial | kpc | -| | distance | | -+-----------------------+-----------------------+-----------------------+ -| isMultiple (_L, | True if the system | N/A | -| \_S) | has companions, | | -| | False if the | | -| | system does not | | -+-----------------------+-----------------------+-----------------------+ -| N_companions (_L, | Number of | N/A | -| \_S) | companions | | -+-----------------------+-----------------------+-----------------------+ -| rem_id (_L, \_S) | Integer indicating | N/A | -| | the remnant object | | -| | type (more details | | -| | in tag | | -| | description) | | -+-----------------------+-----------------------+-----------------------+ -| obj_id (_L, \_S) | Object ID-- unique | N/A | -| | integer to | | -| | identify | | -| | star/compact | | -| | object | | -+-----------------------+-----------------------+-----------------------+ -| ubv_J, H, K, U, I, | UBV photometric | mag | -| B, V, R (_L, \_S) | system, J, H, K, | | -| | U, I, B, V, R | | -| | absolute magnitude | | -+-----------------------+-----------------------+-----------------------+ -| ztf_g, r, i (_L, | ztf photometric | mag | -| \_S) (optional) | system g, r, i | | -| | absoltue magnitude | | -+-----------------------+-----------------------+-----------------------+ -| vr (_L, \_S) | Galactic radial | km/s | -| | velocity | | -+-----------------------+-----------------------+-----------------------+ -| mu_b (_L, \_S) | Galactic proper | mas/yr | -| | motion, b | | -| | component | | -+-----------------------+-----------------------+-----------------------+ -| mu_lcosb (_L, \_S) | Galactic proper | mas/yr | -| | motion, l | | -| | component | | -+-----------------------+-----------------------+-----------------------+ -| theta_E | (Angular) Einstein | mas | -| | radius | | -+-----------------------+-----------------------+-----------------------+ -| mu_rel | Relative | mas/yr | -| | source-lens proper | | -| | motion | | -+-----------------------+-----------------------+-----------------------+ -| u0 | (Unitless) minimum | | dimensionless | -| | source-lens | | (normalized to | -| | separation, | θE) | -| | *during* the | | -| | survey | | -+-----------------------+-----------------------+-----------------------+ -| t0 | Time at which minimum | days | -| | source-lens | | -| | separation occurs | | -+-----------------------+-----------------------+-----------------------+ ++----------------------------+-----------------------+-----------------------+ +| **Tag name** | **Brief | **Units** | +| | Description** | | ++============================+=======================+=======================+ +| zams_mass (_L, | ZAMS mass | M⊙ | +| \_S) | | | ++----------------------------+-----------------------+-----------------------+ +| mass (_L, \_S) | Current mass | M⊙ | ++----------------------------+-----------------------+-----------------------+ +| systemMass (_L, | Sum of mass of | M⊙ | +| \_S) | primary and | | +| | companions (if | | +| | existent) | | ++----------------------------+-----------------------+-----------------------+ +| px (_L, \_S) | Heliocentric x | kpc | +| | position | | ++----------------------------+-----------------------+-----------------------+ +| py (_L, \_S) | Heliocentric y | kpc | +| | position | | ++----------------------------+-----------------------+-----------------------+ +| pz (_L, \_S) | Heliocentric z | kpc | +| | position | | ++----------------------------+-----------------------+-----------------------+ +| vx (_L, \_S) | Heliocentric x | km/s | +| | velocity | | ++----------------------------+-----------------------+-----------------------+ +| vy (_L, \_S) | Heliocentric y | km/s | +| | velocity | | ++----------------------------+-----------------------+-----------------------+ +| vz (_L, \_S) | Heliocentric z | km/s | +| | velocity | | ++----------------------------+-----------------------+-----------------------+ +| age (_L, \_S) | Age | log(age/yr) | ++----------------------------+-----------------------+-----------------------+ +| popid (_L, \_S) | Population ID - | N/A | +| | integer indicating | | +| | the population | | +| | type ranging from | | +| | 0 to 9 | | ++----------------------------+-----------------------+-----------------------+ +| exbv (_L, \_S) | Extinction E(B-V) | mag | +| | at the location of | | +| | star given by 3-D | | +| | Schlegel | | +| | extinction maps | | ++----------------------------+-----------------------+-----------------------+ +| glat (_L, \_S) | Galactic latitude | deg | ++----------------------------+-----------------------+-----------------------+ +| glon (_L, \_S) | Galactic longitude | deg | ++----------------------------+-----------------------+-----------------------+ +| mbol (_L, \_S) | Bolometric | log(L/L⊙) | +| | magnitude | | ++----------------------------+-----------------------+-----------------------+ +| grav (_L, \_S) | Surface gravity | log(gravity) | ++----------------------------+-----------------------+-----------------------+ +| teff (_L, \_S) | Effective | Log(T/Kelvin) | +| | temperature | | ++----------------------------+-----------------------+-----------------------+ +| feh (_L, \_S) | Metallicity | [Fe/H] | ++----------------------------+-----------------------+-----------------------+ +| rad (_L, \_S) | Galactic radial | kpc | +| | distance | | ++----------------------------+-----------------------+-----------------------+ +| isMultiple (_L, | True if the system | N/A | +| \_S) | has companions, | | +| | False if the | | +| | system does not | | ++----------------------------+-----------------------+-----------------------+ +| N_companions (_L, | Number of | N/A | +| \_S) | companions | | ++----------------------------+-----------------------+-----------------------+ +| rem_id (_L, \_S) | Integer indicating | N/A | +| | the remnant object | | +| | type (more details | | +| | in tag | | +| | description) | | ++----------------------------+-----------------------+-----------------------+ +| obj_id (_L, \_S) | Object ID-- unique | N/A | +| | integer to | | +| | identify | | +| | star/compact | | +| | object | | ++----------------------------+-----------------------+-----------------------+ +| ubv_J, H, K, U, I, | UBV photometric | mag | +| B, V, R (_L, \_S) | system, J, H, K, | | +| | U, I, B, V, R | | +| | absolute magnitude | | ++----------------------------+-----------------------+-----------------------+ +| Optional | Additional | mag | +| photsystem_filtname | photometric system | | +| (_L, \_S) | absolute magnitude | | +| (see :ref:`filters`) | | | ++----------------------------+-----------------------+-----------------------+ +| vr (_L, \_S) | Galactic radial | km/s | +| | velocity | | ++----------------------------+-----------------------+-----------------------+ +| mu_b (_L, \_S) | Galactic proper | mas/yr | +| | motion, b | | +| | component | | ++----------------------------+-----------------------+-----------------------+ +| mu_lcosb (_L, \_S) | Galactic proper | mas/yr | +| | motion, l | | +| | component | | ++----------------------------+-----------------------+-----------------------+ +| theta_E | (Angular) Einstein | mas | +| | radius | | ++----------------------------+-----------------------+-----------------------+ +| mu_rel | Relative | mas/yr | +| | source-lens proper | | +| | motion | | ++----------------------------+-----------------------+-----------------------+ +| u0 | (Unitless) minimum | | dimensionless | +| | source-lens | | (normalized to | +| | separation, | θE) | +| | *during* the | | +| | survey | | ++----------------------------+-----------------------+-----------------------+ +| t0 | Time at which minimum | days | +| | source-lens | | +| | separation occurs | | ++----------------------------+-----------------------+-----------------------+ .. @@ -774,9 +799,9 @@ magnitude.) | | U, I, B, V, R | | | | absolute magnitude | | +-----------------------+-----------------------+-----------------------+ -| ztf_g, r, i_N | ztf photometric | mag | -| (optional) | system g, r, i | | -| | absoltue magnitude | | +| Optional | Additional | mag | +| photsystem_filtname_N | photometric system | | +| (see :ref:`filters`) | absolute magnitude | | +-----------------------+-----------------------+-----------------------+ | vr_N | Galactic radial | km/s | | | velocity | | @@ -1269,7 +1294,7 @@ magnitude.) A light curve is symmetric when k = 0. ==================== -5 Coordinate Systems +6 Coordinate Systems ==================== There are two different coordinate systems used, Heliocentric and Galactic. Heliocentric coordinates are Cartesian coordinates with the sun at the origin. The positive :math:`x` axis is pointing toward the Galactic Center, and the positive :math:`z` axis is pointing toward the Galactic North Pole. diff --git a/popsycle/_astropy_init.py b/popsycle/_astropy_init.py index 2dffe8f..c78cb88 100755 --- a/popsycle/_astropy_init.py +++ b/popsycle/_astropy_init.py @@ -18,10 +18,10 @@ if not _ASTROPY_SETUP_: # noqa import os from warnings import warn - from astropy.config.configuration import ( - update_default_config, - ConfigurationDefaultMissingError, - ConfigurationDefaultMissingWarning) + #from astropy.config.configuration import ( + # update_default_config, + # ConfigurationDefaultMissingError, + # ConfigurationDefaultMissingWarning) # Create the test function for self test from astropy.tests.runner import TestRunner diff --git a/popsycle/binary_utils.py b/popsycle/binary_utils.py index 3095401..10e2ae5 100755 --- a/popsycle/binary_utils.py +++ b/popsycle/binary_utils.py @@ -4,6 +4,8 @@ from ast import literal_eval import h5py import pandas as pd +import os +from popsycle import synthetic def add_magnitudes(mags): @@ -227,82 +229,10 @@ def add_observable_peaks_column(t_prim, t_comp_rb, t_comp_rb_mp, t_lightcurves, return t_prim -def cut_Mruns(t_prim, t_comp_rb, t_comp_rb_mp, min_mag, delta_m_cut, u0_cut, ubv_filter, S_LSN): - """ - Make observational cuts on PopSyCLE runs with multiple systems - - Parameters - ---------- - t_prim : Astropy table - Events table from refine_binary_events. - Must contain 'observable_n_peaks' column. - - t_comp_rb : Astropy table - Companion table from refine_binary_events. - t_comp_rb_mp : Astropy table - Multi peak table from refine binary events - (each row corresponds to a peak in a lightcurve). - - min_mag : float - Minimum baseline or source magnitude (specified by S_LSN). - - delta_m_cut : float or None. - Minimum bump magnitude. - - u0_cut : float - Maximum u0. - - ubv_filter : str - Filter name used when cutting on min_mag and delta_m_cut. - - S_LSN : str - 'S' for source mag cut or 'LSN' for baseline magnitude cut. - - Returns - ------- - t_both_mcut : Astropy table - Table with specified observational cuts. - - t_both_mcut_one_peak : Astropy table - Table with specified observational cuts and only single peaked events. - - t_multiples_mcut_multi_peak : Astropy table - Table with specified observational cuts and only multipeaked events - containing a multiple system. - """ - #S_LSN is source or baseline mag cut - if S_LSN == 'S': - mag_cut = t_prim['ubv_{}_app_S'.format(ubv_filter)] <= min_mag - elif S_LSN == 'LSN': - mag_cut = t_prim['ubv_{}_app_LSN'.format(ubv_filter)] <= min_mag - - u0_cut = np.abs(t_prim['u0']) < u0_cut - - binary_filt = (t_prim['isMultiple_L'] == 1) | (t_prim['isMultiple_S'] == 1) - single_filt = (t_prim['isMultiple_L'] == 0) & (t_prim['isMultiple_S'] == 0) - assert(len(t_prim) == (sum(binary_filt) + sum(single_filt))) - - if delta_m_cut is not None: - delta_m_cut = ((t_prim['bin_delta_m'] > 0.1) & binary_filt) | ((t_prim['delta_m_{}'.format(ubv_filter)] > 0.1) & single_filt) - total_cut = mag_cut & u0_cut & delta_m_cut - else: - total_cut = mag_cut & u0_cut - - t_both_mcut = t_prim[total_cut] - binary_filt_cut = (t_both_mcut['isMultiple_L'] == 1) | (t_both_mcut['isMultiple_S'] == 1) - single_filt_cut = (t_both_mcut['isMultiple_L'] == 0) & (t_both_mcut['isMultiple_S'] == 0) - assert(len(t_both_mcut) == (sum(binary_filt_cut) + sum(single_filt_cut))) - - t_mult_mcut_no_peaks = t_both_mcut[binary_filt_cut & (t_both_mcut['observable_n_peaks'] == 0)] - t_both_mcut_one_peak = t_both_mcut[(binary_filt_cut & (t_both_mcut['observable_n_peaks'] == 1)) | single_filt_cut] - t_multiples_mcut_multi_peak = t_both_mcut[binary_filt_cut & (t_both_mcut['observable_n_peaks'] > 1)] - assert(len(t_both_mcut) == (len(t_mult_mcut_no_peaks) + len(t_both_mcut_one_peak) + len(t_multiples_mcut_multi_peak))) - - return t_both_mcut, t_both_mcut_one_peak, t_multiples_mcut_multi_peak def make_bhs_single(hdf5_file, hdf5_comp_file, bh_binary_frac = 0.1, phots = ['ubv_I', 'ubv_K', 'ubv_J', 'ubv_U', 'ubv_R', 'ubv_B', 'ubv_V', 'ubv_H'], - new_hdf5_file = None, new_hdf5_file_comp = None): + new_hdf5_file = None, new_hdf5_file_comp = None, symlink_aux_files = True): """ This makes some fraction of BHs singles. Currently no binary star evolution, so all BHs end up in binaries. @@ -338,6 +268,13 @@ def make_bhs_single(hdf5_file, hdf5_comp_file, bh_binary_frac = 0.1, phots = ['u New hdf5 file name. Default is None which saves it as hdf5_comp_file[:-3] + '_{}_bhb_frac.h5'.format(bh_binary_frac). + + symlink_aux_files : bool + Makes symbolic links to the following necessary auxiliary files with the new root: + _perform_pop_syn.log + _galaxia.log + _galaxia_params.txt + Default is True. """ tmp_prim = h5py.File(hdf5_file, 'r') @@ -348,7 +285,11 @@ def make_bhs_single(hdf5_file, hdf5_comp_file, bh_binary_frac = 0.1, phots = ['u if new_hdf5_file is None: new_hdf5_file = hdf5_file[:-3] + '_{}_bhb_frac.h5'.format(bh_binary_frac) if new_hdf5_file_comp is None: - new_hdf5_file_comp = hdf5_comp_file[:-3] + '_{}_bhb_frac.h5'.format(bh_binary_frac) + new_hdf5_file_comp = hdf5_comp_file[:-13] + '{}_bhb_frac_companions.h5'.format(bh_binary_frac) + if symlink_aux_files: + os.symlink(hdf5_file[:-3] + '_galaxia.log', new_hdf5_file[:-3] + '_galaxia.log') + os.symlink(hdf5_file[:-3] + '_galaxia_params.txt', new_hdf5_file[:-3] + '_galaxia_params.txt') + os.symlink(hdf5_file[:-3] + '_perform_pop_syn.log', new_hdf5_file[:-3] + '_perform_pop_syn.log') prim_copy = h5py.File(new_hdf5_file, 'w') prim_copy[list(keys)[-2]] = tmp_prim[list(keys)[-2]][:] @@ -366,7 +307,8 @@ def make_bhs_single(hdf5_file, hdf5_comp_file, bh_binary_frac = 0.1, phots = ['u for i in list(keys)[1:-2]: if i[0] == 'l': prim = pd.read_hdf(hdf5_file, i).set_index(['obj_id']) - bh_prim = prim[prim['rem_id'] == 103] + bbh_prim_crit = (prim['rem_id'] == 103) & (prim['isMultiple'] == 1) + bh_prim = prim[bbh_prim_crit] #idxs of bhs that will be made single bh_prim_singlify_idxs = np.random.choice(bh_prim.index, size = int(len(bh_prim)*(1-bh_binary_frac)), replace = False) @@ -377,19 +319,53 @@ def make_bhs_single(hdf5_file, hdf5_comp_file, bh_binary_frac = 0.1, phots = ['u for phot in phots: bh_prim.loc[bh_prim_singlify_idxs, (phot)] = np.nan - prim[prim['rem_id'] == 103] = bh_prim + prim[bbh_prim_crit] = bh_prim comp = pd.read_hdf(hdf5_comp_file, i).set_index(['system_idx']) - comp.drop(bh_prim_singlify_idxs) - - with h5py.File(new_hdf5_file, 'r+') as prim_hdf5: - prim_np = prim.reset_index().to_numpy() - prim_hdf5.create_dataset(i, data=prim_np) - - with h5py.File(new_hdf5_file_comp, 'r+') as comp_hdf5: - comp_np = comp.reset_index().to_numpy() - comp_hdf5.create_dataset(i, data=comp_np) + comp.drop(index=bh_prim_singlify_idxs, axis=0, inplace=True) + + # Verify number of companions in table same as accounted for in primary table + assert(len(comp) == np.sum(prim['N_companions'])) + + prim.reset_index(inplace=True) + comp.reset_index(inplace=True) + + prim_hdf5 = h5py.File(new_hdf5_file, 'r+') + compound_dtype = synthetic._generate_compound_dtype(prim.dtypes.to_dict()) + save_data = np.empty(len(prim), dtype=compound_dtype) + for colname in prim.keys(): + save_data[colname] = prim[colname].to_numpy() + dataset = prim_hdf5.create_dataset(i, shape=(0,), + chunks=(1e4,), + maxshape=(None,), + dtype=compound_dtype) + dataset.resize((len(prim),)) + prim_hdf5[i][:] = save_data + prim_hdf5.close() + + del prim, save_data + + comp_hdf5 = h5py.File(new_hdf5_file_comp, 'r+') + compound_dtype = synthetic._generate_compound_dtype(comp.dtypes.to_dict()) + save_data = np.empty(len(comp), dtype=compound_dtype) + for colname in comp.keys(): + save_data[colname] = comp[colname].to_numpy() + dataset = comp_hdf5.create_dataset(i, shape=(0,), + chunks=(1e4,), + maxshape=(None,), + dtype=compound_dtype) + dataset.resize((len(comp),)) + comp_hdf5[i][:] = save_data + comp_hdf5.close() + + del comp, save_data + + #prim_hdf5.create_dataset(i)#, data=prim_np.astype("|V256")) + #with h5py.File(new_hdf5_file_comp, 'r+') as comp_hdf5: + # comp_np = comp.reset_index().to_numpy() + # comp_hdf5.create_dataset(i)#, data=comp_np.astype("|V256")) + return diff --git a/popsycle/data/ubv_to_roman-f062_grid.npz b/popsycle/data/ubv_to_roman-f062_grid.npz new file mode 100755 index 0000000..17c561a Binary files /dev/null and b/popsycle/data/ubv_to_roman-f062_grid.npz differ diff --git a/popsycle/data/ubv_to_roman-f087_grid.npz b/popsycle/data/ubv_to_roman-f087_grid.npz new file mode 100755 index 0000000..bef3fe4 Binary files /dev/null and b/popsycle/data/ubv_to_roman-f087_grid.npz differ diff --git a/popsycle/data/ubv_to_roman-f106_grid.npz b/popsycle/data/ubv_to_roman-f106_grid.npz new file mode 100755 index 0000000..d43d667 Binary files /dev/null and b/popsycle/data/ubv_to_roman-f106_grid.npz differ diff --git a/popsycle/data/ubv_to_roman-f129_grid.npz b/popsycle/data/ubv_to_roman-f129_grid.npz new file mode 100755 index 0000000..905e630 Binary files /dev/null and b/popsycle/data/ubv_to_roman-f129_grid.npz differ diff --git a/popsycle/data/ubv_to_roman-f158_grid.npz b/popsycle/data/ubv_to_roman-f158_grid.npz new file mode 100755 index 0000000..7a7ecfe Binary files /dev/null and b/popsycle/data/ubv_to_roman-f158_grid.npz differ diff --git a/popsycle/data/ubv_to_roman-f184_grid.npz b/popsycle/data/ubv_to_roman-f184_grid.npz new file mode 100755 index 0000000..208392d Binary files /dev/null and b/popsycle/data/ubv_to_roman-f184_grid.npz differ diff --git a/popsycle/data/ubv_to_roman-f213_grid.npz b/popsycle/data/ubv_to_roman-f213_grid.npz new file mode 100755 index 0000000..621e74d Binary files /dev/null and b/popsycle/data/ubv_to_roman-f213_grid.npz differ diff --git a/popsycle/data/ubv_to_roman-w146_grid.npz b/popsycle/data/ubv_to_roman-w146_grid.npz new file mode 100755 index 0000000..c531d82 Binary files /dev/null and b/popsycle/data/ubv_to_roman-w146_grid.npz differ diff --git a/popsycle/data/ubv_to_rubin-g_grid.npz b/popsycle/data/ubv_to_rubin-g_grid.npz new file mode 100755 index 0000000..dbe9e3b Binary files /dev/null and b/popsycle/data/ubv_to_rubin-g_grid.npz differ diff --git a/popsycle/data/ubv_to_rubin-i_grid.npz b/popsycle/data/ubv_to_rubin-i_grid.npz new file mode 100755 index 0000000..c100809 Binary files /dev/null and b/popsycle/data/ubv_to_rubin-i_grid.npz differ diff --git a/popsycle/data/ubv_to_rubin-r_grid.npz b/popsycle/data/ubv_to_rubin-r_grid.npz new file mode 100755 index 0000000..86f76d2 Binary files /dev/null and b/popsycle/data/ubv_to_rubin-r_grid.npz differ diff --git a/popsycle/data/ubv_to_rubin-u_grid.npz b/popsycle/data/ubv_to_rubin-u_grid.npz new file mode 100755 index 0000000..5f8caaf Binary files /dev/null and b/popsycle/data/ubv_to_rubin-u_grid.npz differ diff --git a/popsycle/data/ubv_to_rubin-y_grid.npz b/popsycle/data/ubv_to_rubin-y_grid.npz new file mode 100755 index 0000000..3785c61 Binary files /dev/null and b/popsycle/data/ubv_to_rubin-y_grid.npz differ diff --git a/popsycle/data/ubv_to_rubin-z_grid.npz b/popsycle/data/ubv_to_rubin-z_grid.npz new file mode 100755 index 0000000..0beb180 Binary files /dev/null and b/popsycle/data/ubv_to_rubin-z_grid.npz differ diff --git a/popsycle/ebf.py b/popsycle/ebf.py index 2667619..24b8d48 100755 --- a/popsycle/ebf.py +++ b/popsycle/ebf.py @@ -1131,9 +1131,9 @@ def __expand(self,keyno,keysize): self.close() raise RuntimeError('EBF error: __expand, htable is closed') else: - values1[keys1.index('/.ebf/htable')]=self.data[1] + values1[keys1.index(b'/.ebf/htable')]=self.data[1] for key1,value1 in zip(keys1,values1): - self.__add(key1,value1) + self.__add(str(key1),value1) self.close() self.__setup(self.mode) diff --git a/popsycle/filters.py b/popsycle/filters.py index 32828ec..5df5b3d 100755 --- a/popsycle/filters.py +++ b/popsycle/filters.py @@ -371,3 +371,862 @@ def ztf_mag_AB_to_vega(ztf_mag_AB, filter_name): raise Exception('filter_name must be either g or r or i') return ztf_mag_vega + + +def generate_ubv_to_rubin_grid(iso_dir,filter_name): + """ + Creates the 2D transformational matrix necessary for generating rubin u, g, r, i, z, and y + magnitudes from the UBV filters. + + 2D transformational matrix is valid for values of: + Rubin u: + -2 < ubv_U - ubv_B < 3 + -2 < ubv_B - ubv_V < 3 + -2 < ubv_V - ubv_R < 3 + + Rubin g, r, i, z: + -1 < ubv_B - ubv_V < 2 + -1 < ubv_V - ubv_R < 2 + + Rubin y: + -1 < ubv_R - ubv_I < 4 + -1 < ubv_I - ukirt_J < 4 + + ubv-to-rubin-u + x-axis : ubv_B - ubv_V + y-axis : ubv_U - ubv_B + z-axis : ubv_V - ubv_R + w-axis : rubin_u - ubv_U + + ubv-to-rubin-g + x-axis : ubv_V - ubv_R + y-axis : ubv_B - ubv_V + z-axis : rubin_g - ubv_V + + ubv-to-rubin-r + x-axis : ubv_V - ubv_R + y-axis : ubv_B - ubv_V + z-axis : rubin_r - ubv_R + + ubv-to-rubin-i + x-axis : ubv_V - ubv_R + y-axis : ubv_B - ubv_V + z-axis : rubin_i - ubv_I + + ubv-to-rubin-z + x-axis : ubv_V - ubv_R + y-axis : ubv_B - ubv_V + z-axis : rubin_z - ubv_I + + ubv-to-rubin-y + x-axis : ubv_I - ukirt_J + y-axis : ubv_R - ubv_I + z-axis : rubin_y - ukirt_J + + Parameters + ---------- + iso_dir : filepath + Where are the isochrones stored (for SPISEA) + + filter_name : str + The name of the filter in which to calculate all the + microlensing events. Must be either 'g' or 'r'. + + """ + + logAge = np.log10(8 * 10 ** 9) # Age in log(years) + dist = 10 # distance in parsec + metallicity = 0 # Metallicity in [M/H] + AKs = 0 + + # Define evolution/atmosphere models and extinction law + evo_model = evolution.MISTv1() + atm_func = atmospheres.get_merged_atmosphere + red_law = reddening.RedLawDamineli16() + + # Also specify filters for synthetic photometry + filt_list = ['rubin,u', 'rubin,g', 'rubin,r', 'rubin,i', 'rubin,z', 'rubin,y', + 'ubv,U', 'ubv,B', 'ubv,V', 'ubv,R', 'ubv,I','ukirt,J'] + filt_list_reformat = ['m_%s' % f.replace(',', '_') for f in filt_list] + + ubv_u = np.array([]) + ubv_b = np.array([]) + ubv_v = np.array([]) + ubv_r = np.array([]) + ubv_i = np.array([]) + ukirt_j = np.array([]) + rubin_u = np.array([]) + rubin_g = np.array([]) + rubin_r = np.array([]) + rubin_i = np.array([]) + rubin_z = np.array([]) + rubin_y = np.array([]) + + # Create photometry + my_iso = synthetic.IsochronePhot(logAge, AKs, dist, + metallicity=metallicity, + evo_model=evo_model, + atm_func=atm_func, + red_law=red_law, + filters=filt_list, + iso_dir=iso_dir) + # Check that the isochrone has all of the filters in filt_list + # If not, force recreating the isochrone with recomp=True + iso_filters = [f for f in my_iso.points.colnames if 'm_' in f] + if len(set(filt_list_reformat) - set(iso_filters)) > 0: + my_iso = synthetic.IsochronePhot(logAge, AKs, dist, + metallicity=metallicity, + evo_model=evo_model, + atm_func=atm_func, + red_law=red_law, + filters=filt_list, + iso_dir=iso_dir, + recomp=True) + + clust = my_iso.points + cond = ~np.isnan(clust['m_ubv_V']) + clust = clust[cond] + clust_cond = np.random.choice(np.arange(len(clust)), + size=1000, replace=False) + + ubv_u = np.append(ubv_u, clust['m_ubv_U'][clust_cond]) + ubv_b = np.append(ubv_b, clust['m_ubv_B'][clust_cond]) + ubv_v = np.append(ubv_v, clust['m_ubv_V'][clust_cond]) + ubv_r = np.append(ubv_r, clust['m_ubv_R'][clust_cond]) + ubv_i = np.append(ubv_i, clust['m_ubv_I'][clust_cond]) + ukirt_j = np.append(ukirt_j, clust['m_ukirt_J'][clust_cond]) + rubin_u = np.append(rubin_u, clust['m_rubin_u'][clust_cond]) + rubin_g = np.append(rubin_g, clust['m_rubin_g'][clust_cond]) + rubin_r = np.append(rubin_r, clust['m_rubin_r'][clust_cond]) + rubin_i = np.append(rubin_i, clust['m_rubin_i'][clust_cond]) + rubin_z = np.append(rubin_z, clust['m_rubin_z'][clust_cond]) + rubin_y = np.append(rubin_y, clust['m_rubin_y'][clust_cond]) + + # Given the filter name, define a difference in magnitude to be fit for + if filter_name == 'u': + delta_m = rubin_u - ubv_u + elif filter_name == 'g': + delta_m = rubin_g - ubv_v + elif filter_name == 'r': + delta_m = rubin_r - ubv_r + elif filter_name == 'i': + delta_m = rubin_i - ubv_i + elif filter_name == 'z': + delta_m = rubin_z - ubv_i + elif filter_name == 'y': + delta_m = rubin_y - ukirt_j + + + # Colors in both x and y direction go from 0 to 6 magnitudes + + if filter_name == 'u': + x_grid_arr = np.linspace(-2, 3, 100) + y_grid_arr = np.linspace(-2, 3, 100) + z_grid_arr = np.linspace(-2, 3, 100) + elif filter_name == 'y': + x_grid_arr = np.linspace(-1, 4, 1000) + y_grid_arr = np.linspace(-1, 4, 1000) + else: + x_grid_arr = np.linspace(-1, 2, 1000) + y_grid_arr = np.linspace(-1, 2, 1000) + + # Create a grid of values on x_grid_arr and y_grid_arr + # with linear algorithm + if filter_name == 'u': + x,y,z = (ubv_b - ubv_v, ubv_u - ubv_b, ubv_v - ubv_r) + + elif filter_name == 'y': + x,y = (ubv_i - ukirt_j, ubv_r - ubv_i) + else: + x,y = (ubv_v - ubv_r, ubv_b - ubv_v) + + if filter_name == 'u': + ubv_to_rubin_grid = griddata((x,y,z), delta_m, + (x_grid_arr[:,None,None], y_grid_arr[None,:, None], z_grid_arr[None,None,:]), + method='linear') + + # Resample this grid with both the liner and nearest algorithms onto a + # finer grid. This allows for the 'nearest' method to + # create fewer artifacts + xx, yy, zz = np.meshgrid(x_grid_arr, y_grid_arr, z_grid_arr) + xx, yy, zz = xx.flatten(), yy.flatten(), zz.flatten() + + cond = ~np.isnan(ubv_to_rubin_grid.flatten()) + ubv_to_rubin_grid_filled = griddata((xx[cond], yy[cond], zz[cond]), + ubv_to_rubin_grid.flatten()[cond], + (x_grid_arr[:,None,None], + y_grid_arr[None,:, None], + z_grid_arr[None,None,:]), + method='linear') + + ubv_to_rubin_grid_nearest = griddata((xx[cond], yy[cond], zz[cond]), + ubv_to_rubin_grid.flatten()[cond], + (x_grid_arr[:,None,None], + y_grid_arr[None,:, None], + z_grid_arr[None,None,:]), + method='nearest') + + # Place values into final grid from linear algorthm, and from the + # nearest algorithm where the linear algorithm could not find a solution + cond = np.isnan(ubv_to_rubin_grid_filled) + ubv_to_rubin_grid_final = np.zeros_like(ubv_to_rubin_grid_filled) + ubv_to_rubin_grid_final[cond] = ubv_to_rubin_grid_nearest[cond] + ubv_to_rubin_grid_final[~cond] = ubv_to_rubin_grid_filled[~cond] + grid_arr = np.squeeze(np.dstack([xx, yy, zz]), axis=0) + + else: + ubv_to_rubin_grid = griddata((x,y), delta_m, + (x_grid_arr[None, :], y_grid_arr[:, None]), + method='linear') + + # Resample this grid with both the liner and nearest algorithms onto a + # finer grid. This allows for the 'nearest' method to + # create fewer artifacts + xx, yy = np.meshgrid(x_grid_arr, y_grid_arr) + xx, yy = xx.flatten(), yy.flatten() + + cond = ~np.isnan(ubv_to_rubin_grid.flatten()) + ubv_to_rubin_grid_filled = griddata((xx[cond], yy[cond]), + ubv_to_rubin_grid.flatten()[cond], + (x_grid_arr[None, :], + y_grid_arr[:, None]), + method='linear') + + ubv_to_rubin_grid_nearest = griddata((xx[cond], yy[cond]), + ubv_to_rubin_grid.flatten()[cond], + (x_grid_arr[None, :], + y_grid_arr[:, None]), + method='nearest') + + # Place values into final grid from linear algorthm, and from the + # nearest algorithm where the linear algorithm could not find a solution + cond = np.isnan(ubv_to_rubin_grid_filled) + ubv_to_rubin_grid_final = np.zeros_like(ubv_to_rubin_grid_filled) + ubv_to_rubin_grid_final[cond] = ubv_to_rubin_grid_nearest[cond] + ubv_to_rubin_grid_final[~cond] = ubv_to_rubin_grid_filled[~cond] + grid_arr = np.squeeze(np.dstack([xx, yy]), axis=0) + + # Save the data + data_dir = '%s/data' % os.path.dirname(inspect.getfile(generate_ubv_to_rubin_grid)) + ubv_to_rubin_filename = '%s/ubv_to_rubin-%s_grid.npz' % (data_dir, filter_name) + np.savez(ubv_to_rubin_filename, + ubv_to_rubin_grid=ubv_to_rubin_grid_final.astype(np.float32), + kdtree_grid=grid_arr.astype(np.float32)) + +def load_ubv_to_rubin_grid(filter_name): + # Check for correct filter + if filter_name not in ['u','g', 'r', 'i', 'z', 'y']: + raise Exception("filter_name must be in: ['u','g', 'r', 'i', 'z', 'y']") + + # Load the ubv_to_rubin_grid from the file + data_dir = '%s/data' % os.path.dirname(inspect.getfile(load_ubv_to_rubin_grid)) + ubv_to_rubin_filename = '%s/ubv_to_rubin-%s_grid.npz' % (data_dir, filter_name) + ubv_to_rubin_grid_file = np.load(ubv_to_rubin_filename) + + # Generate a kdtree at the locations of all of the grid points + ubv_to_rubin_grid = ubv_to_rubin_grid_file['ubv_to_rubin_grid'] + kdtree = cKDTree(ubv_to_rubin_grid_file['kdtree_grid']) + + return ubv_to_rubin_grid, kdtree + +def transform_ubv_to_rubin(filter_name, ubv_B, ubv_V, ubv_R, ubv_U=None, ubv_I=None, ukirt_J=None): + """ + Converts ubv filters into rubin filters. + + Function is valid for values of: + For filter u: + -2 < ubv_U - ubv_B < 3 + -2 < ubv_B - ubv_V < 3 + -2 < ubv_V - ubv_R < 3 + + For filters g, r, i, and z: + -1 < ubv_B - ubv_V < 2 + -1 < ubv_V - ubv_R < 2 + + For filter y: + -1 < ubv_R - ubv_I < 4 + -1 < ubv_I - ukirt_J < 4 + + Parameters + ---------- + filter_name : str + rubin filter name of converted photometry + Can be 'u', 'g', 'r', 'i', 'z', or 'y' + If converting to rubin_u, then ubv_U must be provided + If converting to rubin_i or rubin_z, then ubv_I must be provided + If converting to rubin_y, then ukirt_J must be provided + + ubv_B : array of floats + ubv_B photometry of galaxia / SPISEA sources + + ubv_V : array of floats + ubv_V photometry of galaxia / SPISEA sources + + ubv_R : array of floats + ubv_R photometry of galaxia / SPISEA sources + + ubv_U : array of floats, optional + ubv_U photometry of galaxia / SPISEA sources, + required for converting to rubin_u + + ubv_I : array of floats, optional + ubv_I photometry of galaxia / SPISEA sources, + required for converting to rubin_i and rubin_z + + ukirt_J : array of floats, optional + ukirt_J photometry of galaxia / SPISEA sources, + required for converting to rubin_y + + Returns + ------- + rubin_mag : array of floats + rubin photometry of galaxia / SPISEA sources in `filter_name` + + """ + # Check for correct filter + if filter_name not in ['u','g', 'r', 'i', 'z', 'y']: + raise Exception("filter_name must be in: ['u','g', 'r', 'i', 'z', 'y']") + + if filter_name == 'i' and ubv_I is None: + raise Exception('ubv_I must be provided to convert to rubin_i') + elif filter_name == 'z' and ubv_I is None: + raise Exception('ubv_I must be provided to convert to rubin_z') + elif filter_name == 'u' and ubv_U is None: + raise Exception('ubv_U and sdss_U must be provided to convert to rubin_u') + elif filter_name == 'y' and ubv_I is None: + raise Exception('ubv_I and ukirt_J must be provided to convert to rubin_y') + elif filter_name =='y' and ukirt_J is None: + raise Exception('ubv_I and ukirt_J must be provided to convert to rubin_y') + + + # Convert the ubv photometry into the right format + if filter_name == 'u': + x_data = ubv_B - ubv_V + y_data = ubv_U - ubv_B + z_data = ubv_V - ubv_R + elif filter_name == 'y': + x_data = ubv_I - ukirt_J + y_data = ubv_R - ubv_I + else: + x_data = ubv_V - ubv_R + y_data = ubv_B- ubv_V + + if filter_name == 'u': + data = np.squeeze(np.dstack([x_data, y_data, z_data]), axis=0) + else: + data = np.squeeze(np.dstack([x_data, y_data]), axis=0) + + # Only query on data that is luminous + cond_lum = ~np.isnan(data).any(axis=1) + + # Start with an empty array of nans + rubin_diff = np.full(len(ubv_B), np.nan) + + # Find locations on the grid where x_data and y_data are located. + # Put those values into the rubin_diff arrays + ubv_to_rubin_grid, kdtree = load_ubv_to_rubin_grid(filter_name) + _, indexes = kdtree.query(data[cond_lum]) + rubin_diff[cond_lum] = ubv_to_rubin_grid.flatten()[indexes] + + # Convert to rubin filter + if filter_name == 'u': + rubin_mag = ubv_U + rubin_diff + elif filter_name == 'g': + rubin_mag = ubv_V + rubin_diff + elif filter_name == 'r': + rubin_mag = ubv_R + rubin_diff + elif filter_name == 'i': + rubin_mag = ubv_I + rubin_diff + elif filter_name == 'z': + rubin_mag = ubv_I + rubin_diff + elif filter_name == 'y': + rubin_mag = ukirt_J + rubin_diff + + return rubin_mag + +def generate_ubv_to_roman_grid(iso_dir, filter_name): + """ + Creates the 2D transformational matrix necessary for generating + roman f062, f087, f106, f129, f158, f146, f184, and f213 + magnitudes from the UBV filters. + + 2D transformational matrix is valid for values of: + + Roman f062: + -0.5 < ubv_V - ubv_R < 3.5 + -0.5 < ubv_R - ubv_I < 3.5 + + Roman f087, f106: + -0.5 < ubv_R - ubv_I < 3.5 + -0.5 < ubv_I - ukirt_J < 3.5 + + Roman f129: + -0.5 < ubv_I - ukirt_J < 2.5 + -0.5 < ukirt_J - ukirt_H < 2.5 + + Roman f146 - equivalent to w146: + -0.5 < ubv_I - ukirt_H < 3.5 + -0.5 < ukirt_H - ukirt_K < 3.5 + + Roman f158, f184, f213: + -0.5 < ukirt_J - ukirt_H < 1.5 + -0.5 < ukirt_H - ukirt_K < 1.5 + + ubv-to-roman-f062 + x-axis : ubv_V - ubv_R + y-axis : ubv_R - ubv_I + z-axis : roman_f062 - ubv_R + + ubv-to-roman-f087 + x-axis : ubv_R - ubv_I + y-axis : ubv_I - ukirt_J + z-axis : roman_f087 - ubv_I + + ubv-to-roman-f106 + x-axis : ubv_R - ubv_I + y-axis : ubv_I - ukirt_J + z-axis : roman_i - ubv_I + + ubv-to-roman-f129 + x-axis : ubv_I - ukirt_J + y-axis : ukirt_J - ukirt_H + z-axis : roman_f129 - ukirt_J + + ubv-to-roman-f158 + x-axis : ukirt_J - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f158 - ukirt_H + + ubv-to-roman-f146 + x-axis : ubv_I - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f146 - ukirt_H + + ubv-to-roman-f184 + x-axis : ukirt_J - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f184 - ukirt_H + + ubv-to-roman-f213 + x-axis : ukirt_J - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f213 - ukirt_K + + Parameters + ---------- + iso_dir : filepath + Where are the isochrones stored (for SPISEA) + + filter_name : str + The name of the filter in which to calculate all the + microlensing events. Must be either + 'f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146'. + + """ + + # Define isochrone parameters for calculating absolute magnitudes + logAge = np.log10(8 * 10 ** 9) # Age in log(years) + dist = 10 # distance in parsec + metallicity = 0 # Metallicity in [M/H] + + # Define evolution/atmosphere models and extinction law + evo_model = evolution.MISTv1() + atm_func = atmospheres.get_merged_atmosphere + red_law = reddening.RedLawDamineli16() + + # Also specify filters for synthetic photometry + filt_list = ['roman,wfi,f062', 'roman,wfi,f087', 'roman,wfi,f106', 'roman,wfi,f129', + 'roman,wfi,f158', 'roman,wfi,f146', 'roman,wfi,f184', 'roman,wfi,f213', + 'ubv,V', 'ubv,R', 'ubv,I', 'ukirt,J', 'ukirt,H', 'ukirt,K'] + filt_list_reformat = ['m_%s' % f.replace(',wfi,', '_') if f[:5] == 'roman' else 'm_%s' % f.replace(',','_') for f in filt_list] + + # Make multiplicity object + imf_multi = multiplicity.MultiplicityUnresolved() + + # Make IMF object; we'll use a broken power law with the parameters from Kroupa+01 + # Define boundaries of each mass segement + massLimits = np.array([0.08, 0.5, 1, 120]) + # Power law slope associated with each mass segment + powers = np.array([-1.3, -2.3, -2.3]) + my_imf = imf.IMF_broken_powerlaw(massLimits, powers, imf_multi) + + # Define total cluster mass + mass = 10 ** 5. + + ubv_u = np.array([]) + ubv_b = np.array([]) + ubv_v = np.array([]) + ubv_r = np.array([]) + ubv_i = np.array([]) + ukirt_j = np.array([]) + ukirt_h = np.array([]) + ukirt_k = np.array([]) + roman_f062 = np.array([]) + roman_f087 = np.array([]) + roman_f106 = np.array([]) + roman_f129 = np.array([]) + roman_f158 = np.array([]) + roman_f146 = np.array([]) + roman_f184 = np.array([]) + roman_f213 = np.array([]) + + # Create photometry for a range of extinctions + AKs = 0 + my_iso = synthetic.IsochronePhot(logAge, AKs, dist, + metallicity=metallicity, + evo_model=evo_model, + atm_func=atm_func, + red_law=red_law, + filters=filt_list, + iso_dir=iso_dir) + # Check that the isochrone has all of the filters in filt_list + # If not, force recreating the isochrone with recomp=True + iso_filters = [f for f in my_iso.points.colnames if 'm_' in f] + + if len(set(filt_list_reformat) - set(iso_filters)) > 0: + my_iso = synthetic.IsochronePhot(logAge, AKs, dist, + metallicity=metallicity, + evo_model=evo_model, + atm_func=atm_func, + red_law=red_law, + filters=filt_list, + iso_dir=iso_dir, + recomp=True) + # Make cluster object + cluster = synthetic.ResolvedCluster(my_iso, my_imf, mass, + ifmr=None) + clust = my_iso.points + cond = ~np.isnan(clust['m_ubv_V']) + clust = clust[cond] +# clust_cond = np.random.choice(np.arange(len(clust)), +# size=10000, replace=False) + + ubv_v = np.append(ubv_v, clust['m_ubv_V'])#[clust_cond]) + ubv_r = np.append(ubv_r, clust['m_ubv_R'])#[clust_cond]) + ubv_i = np.append(ubv_i, clust['m_ubv_I'])#[clust_cond]) + ukirt_j = np.append(ukirt_j, clust['m_ukirt_J'])#[clust_cond]) + ukirt_h = np.append(ukirt_h, clust['m_ukirt_H'])#[clust_cond]) + ukirt_k = np.append(ukirt_k, clust['m_ukirt_K'])#[clust_cond]) + roman_f062 = np.append(roman_f062, clust['m_roman_f062'])#[clust_cond]) + roman_f087 = np.append(roman_f087, clust['m_roman_f087'])#[clust_cond]) + roman_f106 = np.append(roman_f106, clust['m_roman_f106'])#[clust_cond]) + roman_f129 = np.append(roman_f129, clust['m_roman_f129'])#[clust_cond]) + roman_f158 = np.append(roman_f158, clust['m_roman_f158'])#[clust_cond]) + roman_f146 = np.append(roman_f146, clust['m_roman_f146'])#[clust_cond]) + roman_f184 = np.append(roman_f184, clust['m_roman_f184'])#[clust_cond]) + roman_f213 = np.append(roman_f213, clust['m_roman_f213'])#[clust_cond]) + + # Given the filter name, define a difference in magnitude to be fit for + if filter_name == 'f062': + delta_m = roman_f062 - ubv_r + elif filter_name == 'f087': + delta_m = roman_f087 - ubv_i + elif filter_name == 'f106': + delta_m = roman_f106 - ubv_i + elif filter_name == 'f129': + delta_m = roman_f129 - ukirt_j + elif filter_name == 'f158': + delta_m = roman_f158 - ukirt_h + elif filter_name == 'f146': + delta_m = roman_f146 - ukirt_h + elif filter_name == 'f184': + delta_m = roman_f184 - ukirt_h + elif filter_name == 'f213': + delta_m = roman_f213 - ukirt_k + else: + raise Exception("filter_name must be in ['f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146']") + + + + # Colors in both x and y direction go from 0 to 6 magnitudes + # x_grid_arr: ubv_v - ubv_r + # y_grid_arr: ubv_b - ubv_v + if filter_name == 'f062' or filter_name == 'f087' or filter_name == 'f146' or filter_name == 'f106': + x_grid_arr = np.linspace(-0.5, 3.5, 1000) + y_grid_arr = np.linspace(-0.5, 3.5, 1000) + elif filter_name == 'f129': + x_grid_arr = np.linspace(-0.5, 2.5, 1000) + y_grid_arr = np.linspace(-0.5, 2.5, 1000) + else: + x_grid_arr = np.linspace(-0.5, 1.5, 1000) + y_grid_arr = np.linspace(-0.5, 1.5, 1000) + + # Create a grid of values on x_grid_arr and y_grid_arr + # with linear algorithm + + if filter_name == 'f062': + x, y = ubv_v - ubv_r, ubv_r - ubv_i + elif filter_name == 'f087': + x, y = ubv_r - ubv_i, ubv_i - ukirt_j + elif filter_name == 'f106': + x, y = ubv_r - ubv_i, ubv_i - ukirt_j + elif filter_name == 'f129': + x, y = ubv_i - ukirt_j, ukirt_j - ukirt_h + elif filter_name == 'f146': + x, y = ubv_i - ukirt_h, ukirt_h - ukirt_k + else: + x, y = ukirt_j - ukirt_h, ukirt_h - ukirt_k + + ubv_to_roman_grid = griddata((x, y), + delta_m, + (x_grid_arr[None, :], y_grid_arr[:, None]), + method='linear') + + # Resample this grid with both the liner and nearest algorithms onto a + # finer grid. This allows for the 'nearest' method to + # create fewer artifacts + xx, yy = np.meshgrid(x_grid_arr, y_grid_arr) + xx, yy = xx.flatten(), yy.flatten() + + cond = ~np.isnan(ubv_to_roman_grid.flatten()) + ubv_to_roman_grid_filled = griddata((xx[cond], yy[cond]), + ubv_to_roman_grid.flatten()[cond], + (x_grid_arr[None, :], + y_grid_arr[:, None]), + method='linear') + + ubv_to_roman_grid_nearest = griddata((xx[cond], yy[cond]), + ubv_to_roman_grid.flatten()[cond], + (x_grid_arr[None, :], + y_grid_arr[:, None]), + method='nearest') + + # Place values into final grid from linear algorthm, and from the + # nearest algorithm where the linear algorithm could not find a solution + cond = np.isnan(ubv_to_roman_grid_filled) + ubv_to_roman_grid_final = np.zeros_like(ubv_to_roman_grid_filled) + ubv_to_roman_grid_final[cond] = ubv_to_roman_grid_nearest[cond] + ubv_to_roman_grid_final[~cond] = ubv_to_roman_grid_filled[~cond] + + # Save the data + grid_arr = np.squeeze(np.dstack([xx, yy]), axis=0) + data_dir = '%s/data' % os.path.dirname(inspect.getfile(generate_ubv_to_roman_grid)) + ubv_to_roman_filename = '%s/ubv_to_roman-%s_grid.npz' % (data_dir, filter_name) + np.savez(ubv_to_roman_filename, + ubv_to_roman_grid=ubv_to_roman_grid_final.astype(np.float32), + kdtree_grid=grid_arr.astype(np.float32)) + + +def load_ubv_to_roman_grid(filter_name): + """ + Loads the 2D transformational matrix necessary for generating + roman f062, f087, f106, f129, f158, f146, f184, and f213 + magnitudes from the UBV filters, as well as the kdtree of those values. + + 2D transformational matrix is valid for values of: + + Roman f062: + -0.5 < ubv_V - ubv_R < 3.5 + -0.5 < ubv_R - ubv_I < 3.5 + + Roman f087, f106: + -0.5 < ubv_R - ubv_I < 3.5 + -0.5 < ubv_I - ukirt_J < 3.5 + + Roman f129: + -0.5 < ubv_I - ukirt_J < 2.5 + -0.5 < ukirt_J - ukirt_H < 2.5 + + Roman f146 - same as w146: + -0.5 < ubv_I - ukirt_H < 3.5 + -0.5 < ukirt_H - ukirt_K < 3.5 + + Roman f158, f184, f213: + -0.5 < ukirt_J - ukirt_H < 1.5 + -0.5 < ukirt_H - ukirt_K < 1.5 + + ubv-to-roman-f062 + x-axis : ubv_V - ubv_R + y-axis : ubv_R - ubv_I + z-axis : roman_f062 - ubv_R + + ubv-to-roman-f087 + x-axis : ubv_R - ubv_I + y-axis : ubv_I - ukirt_J + z-axis : roman_f087 - ubv_I + + ubv-to-roman-f106 + x-axis : ubv_R - ubv_I + y-axis : ubv_I - ukirt_J + z-axis : roman_i - ubv_I + + ubv-to-roman-f129 + x-axis : ubv_I - ukirt_J + y-axis : ukirt_J - ukirt_H + z-axis : roman_f129 - ukirt_J + + ubv-to-roman-f158 + x-axis : ukirt_J - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f158 - ukirt_H + + ubv-to-roman-f146 + x-axis : ubv_I - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f146 - ukirt_H + + ubv-to-roman-f184 + x-axis : ukirt_J - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f184 - ukirt_H + + ubv-to-roman-f213 + x-axis : ukirt_J - ukirt_H + y-axis : ukirt_H - ukirt_K + z-axis : roman_f213 - ukirt_K + + Parameters + ---------- + filter_name : str + The name of the filter in which to calculate all the + microlensing events. Must be either + 'f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146'. + + Returns + ------- + ubv_to_roman_grid : 2D numpy array + 2D grid array of UBV colors with each cell containing the difference + between a roman filter and a ubv filter + + kdtree : cKDTree + kdtree containing the grid of colors on the x-axis and y-axis + + """ + # Check for correct filter + if filter_name not in ['f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146']: + raise Exception("filter_name must be in: ['f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146']") + + # Load the ubv_to_roman_grid from the file + data_dir = '%s/data' % os.path.dirname(inspect.getfile(load_ubv_to_roman_grid)) + if filter_name == 'f146': + ubv_to_roman_filename = '%s/ubv_to_roman-w146_grid.npz' % (data_dir) + else: + ubv_to_roman_filename = '%s/ubv_to_roman-%s_grid.npz' % (data_dir, filter_name) + ubv_to_roman_grid_file = np.load(ubv_to_roman_filename) + + # Generate a kdtree at the locations of all of the grid points + ubv_to_roman_grid = ubv_to_roman_grid_file['ubv_to_roman_grid'] + kdtree = cKDTree(ubv_to_roman_grid_file['kdtree_grid']) + + return ubv_to_roman_grid, kdtree + + +def transform_ubv_to_roman(filter_name, ukirt_J, ukirt_H, ukirt_K, ubv_V=None, ubv_R=None, ubv_I=None): + """ + Converts ubv filters into roman filters. + + 2D transformational matrix is valid for values of: + + Roman f062: + -0.5 < ubv_V - ubv_R < 3.5 + -0.5 < ubv_R - ubv_I < 3.5 + + Roman f087, f106: + -0.5 < ubv_R - ubv_I < 3.5 + -0.5 < ubv_I - ukirt_J < 3.5 + + Roman f129: + -0.5 < ubv_I - ukirt_J < 2.5 + -0.5 < ukirt_J - ukirt_H < 2.5 + + Roman f146 - equivalent to w146: + -0.5 < ubv_I - ukirt_H < 3.5 + -0.5 < ukirt_H - ukirt_K < 3.5 + + Roman f158, f184, f213: + -0.5 < ukirt_J - ukirt_H < 1.5 + -0.5 < ukirt_H - ukirt_K < 1.5 + + Parameters + ---------- + filter_name : str + roman filter name of converted photometry + Can be 'f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146' + If converting to roman_f062, ubv_V, ubv_R, and ubv_I must be provided + If converting to roman_f087 or roman_f106, ubv_R and ubv_I must be provided + If converting to roman_129 or roman_f146, ubv_I must be provided + + ubv_V : array of floats + ubv_V photometry of galaxia / SPISEA sources + + ubv_R : array of floats + ubv_R photometry of galaxia / SPISEA sources + + ubv_I : array of floats + ubv_I photometry of galaxia / SPISEA sources + + ukirt_J : array of floats + ukirt_J photometry of galaxia / SPISEA sources + + ukirt_H : array of floats + ukirt_H photometry of galaxia / SPISEA sources + + ukirt_K : array of floats + ukirt_K photometry of galaxia / SPISEA sources + + Returns + ------- + roman_mag : array of floats + roman photometry of galaxia / SPISEA sources in `filter_name` + + """ + # Check for correct filter + if filter_name not in ['f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146']: + raise Exception("filter_name must be in: ['f062', 'f087', 'f106', 'f129', 'f158', 'f184', 'f213', 'f146']") + + if filter_name == 'f062' and (ubv_V is None or ubv_R is None or ubv_I is None): + raise Exception('ubv_V, ubv_R, and ubv_I must be provided to convert to roman_f062') + elif filter_name == 'f087' and (ubv_R is None or ubv_I is None): + raise Exception('ubv_R and ubv_I must be provided to convert to roman_f087') + elif filter_name == 'f106' and (ubv_R is None or ubv_I is None): + raise Exception('ubv_R and ubv_I must be provided to convert to roman_f106') + elif filter_name == 'f129' and ubv_I is None: + raise Exception('ubv_I must be provided to convert to roman_f129') + elif filter_name == 'f146' and ubv_I is None: + raise Exception('ubv_I must be provided to convert to roman_f146') + + + # Convert the ubv photometry into the right format + + if filter_name == 'f062': + x_data, y_data = ubv_V - ubv_R, ubv_R - ubv_I + elif filter_name == 'f087': + x_data, y_data = ubv_R - ubv_I, ubv_I - ukirt_J + elif filter_name == 'f106': + x_data, y_data = ubv_R - ubv_I, ubv_I - ukirt_J + elif filter_name == 'f129': + x_data, y_data = ubv_I - ukirt_J, ukirt_J - ukirt_H + elif filter_name == 'f146': + x_data, y_data = ubv_I - ukirt_H, ukirt_H - ukirt_K + else: + x_data, y_data = ukirt_J - ukirt_H, ukirt_H - ukirt_K + + data = np.squeeze(np.dstack([x_data, y_data]), axis=0) + + # Only query on data that is luminous + cond_lum = ~np.isnan(data).any(axis=1) + + # Start with an empty array of nans + roman_diff = np.full(len(ubv_R), np.nan) + + # Find locations on the grid where x_data and y_data are located. + # Put those values into the roman_diff arrays + ubv_to_roman_grid, kdtree = load_ubv_to_roman_grid(filter_name) + _, indexes = kdtree.query(data[cond_lum]) + roman_diff[cond_lum] = ubv_to_roman_grid.flatten()[indexes] + + # Convert to roman_g, roman_r or roman_i + if filter_name == 'f062': + roman_mag = ubv_R + roman_diff + elif filter_name == 'f087': + roman_mag = ubv_I + roman_diff + elif filter_name == 'f106': + roman_mag = ubv_I + roman_diff + elif filter_name == 'f129': + roman_mag = ukirt_J + roman_diff + elif filter_name == 'f213': + roman_mag = ukirt_K + roman_diff + else: + roman_mag = ukirt_H + roman_diff + + return roman_mag \ No newline at end of file diff --git a/popsycle/lightcurves.py b/popsycle/lightcurves.py index 0e5e9b0..a9d73e2 100644 --- a/popsycle/lightcurves.py +++ b/popsycle/lightcurves.py @@ -5,7 +5,8 @@ from astropy.coordinates import SkyCoord from astropy import units as unit from astropy.table import Table -from popsycle import synthetic, binary_utils +from astropy import table +from popsycle import synthetic, binary_utils, phot_utils from multiprocessing import Pool, Value, Lock # def get_used_lightcurve_companions(event_table, comp_table, lcurve_table): @@ -78,59 +79,64 @@ def get_bagle_model_list(event_table, comp_table, lcurve_table, - photometric_system, filter_name, red_law, n_multi_proc=6): + photometric_system, filter_name, red_law, n_multi_proc=6, + return_name_and_dict = False): """ - Create BAGLE model instances for table of events. - Parameters ---------- - event_table : astropy.table.Table - Event table from synthetic.py refine_events. + event_table: Table + The table containing event data such as object IDs for lenses and sources. - comp_table : astropy.table.Table - Companions table that contains all lens or source companions for the events table. + comp_table: Table + The table containing companion data. - lcurve_table : astropy.table.Table - Lightcurves generated for the binary events (from refine_binary_events). This is needed - to figure out which of the companions (in the case of triples) is used in advance. + lcurve_table: Table or None + The table containing light curve data associated with the events. - photometric_system : str - Name of the photometric system, i.e. 'ubv'. + photometric_system: str + The photometric system to be used for modeling. - filter_name : str - Name of filter associated with photometric system, i.e. 'I'. + filter_name: str + The name of the filter to be used in the photometric system. red_law : str Name of reddening law in filt_dict list above, i.e. 'Damineli16'. + return_name_and_dict : bool, Optional + If True, returns name of model and parameter dict list instead of model list. + Default is False. + Returns ------- A list of BAGLE model instances. + n_multi_proc: int, optional, default=6 + The number of multiprocessing processes to be used for model generation. """ # Set event table index for easier cross-matching. event_table_df = event_table.to_pandas().set_index(['obj_id_L', 'obj_id_S'], drop=False) # Convert other tables to pandas. - lcurv_table_df = lcurve_table.to_pandas() - comps_table_df = comp_table.to_pandas() + if lcurve_table is not None: + lcurv_table_df = lcurve_table.to_pandas() + comps_table_df = comp_table.to_pandas() - # Group lightcurves associated with the same event. - grouped_lcurv = lcurv_table_df.groupby(['obj_id_L', 'obj_id_S']) + # Group lightcurves associated with the same event. + grouped_lcurv = lcurv_table_df.groupby(['obj_id_L', 'obj_id_S']) - # Filter each group to just the companions used in the lightcurve. - # Should be 1 lens companion for PSBL, 1 source companion for BSPL, 1 lens + 1 source for BSBL - lcurv_used = lcurv_table_df.loc[grouped_lcurv['used_lightcurve'].idxmax()] + # Filter each group to just the companions used in the lightcurve. + # Should be 1 lens companion for PSBL, 1 source companion for BSPL, 1 lens + 1 source for BSBL + lcurv_used = lcurv_table_df.loc[grouped_lcurv['used_lightcurve'].idxmax()] - # Clean up columns that should be ints not floats and set index. - lcurv_used['obj_id_L'] = lcurv_used['obj_id_L'].astype('int') - lcurv_used['obj_id_S'] = lcurv_used['obj_id_S'].astype('int') - lcurv_used.set_index(['obj_id_L', 'obj_id_S'], inplace=True) + # Clean up columns that should be ints not floats and set index. + lcurv_used['obj_id_L'] = lcurv_used['obj_id_L'].astype('int') + lcurv_used['obj_id_S'] = lcurv_used['obj_id_S'].astype('int') + lcurv_used.set_index(['obj_id_L', 'obj_id_S'], inplace=True) - comps_table_df['companion_idx'] = comps_table_df['companion_idx'].astype('float') + comps_table_df['companion_idx'] = comps_table_df['companion_idx'].astype('float') - # Group the companions by the index for easier access. - grouped_comps = comps_table_df.groupby(['obj_id_L', 'obj_id_S']) + # Group the companions by the index for easier access. + grouped_comps = comps_table_df.groupby(['obj_id_L', 'obj_id_S']) # Split up the events and companions for use in multiprocessing inputs = np.empty(len(event_table), dtype=object) @@ -139,20 +145,23 @@ def get_bagle_model_list(event_table, comp_table, lcurve_table, event_i = Table.from_pandas(event_table_df.iloc[[i]]) # return astropy table, not series index_i = event_table_df.index[i] - try: - # Get the used lightcurve row. - lcurve_i = lcurv_used.loc[index_i] + if ((lcurve_table is None) or (comp_table is None)): + inputs[i] = [event_i, None, photometric_system, filter_name, red_law, return_name_and_dict] + else: + try: + # Get the used lightcurve row. + lcurve_i = lcurv_used.loc[index_i] - # Get the individual companions associated with this used lightcurve. - comps_i_all = grouped_comps.get_group(index_i) - comps_i = comps_i_all.loc[(comps_i_all['companion_idx'] == lcurve_i['companion_id_L']) | - (comps_i_all['companion_idx'] == lcurve_i['companion_id_S'])] - comps_i = Table.from_pandas(comps_i) + # Get the individual companions associated with this used lightcurve. + comps_i_all = grouped_comps.get_group(index_i) + comps_i = comps_i_all.loc[(comps_i_all['companion_idx'] == lcurve_i['companion_id_L']) | + (comps_i_all['companion_idx'] == lcurve_i['companion_id_S'])] + comps_i = Table.from_pandas(comps_i) - except KeyError: - comps_i = None + except KeyError: + comps_i = None - inputs[i] = [event_i, comps_i, photometric_system, filter_name, red_law] + inputs[i] = [event_i, comps_i, photometric_system, filter_name, red_law, return_name_and_dict] if n_multi_proc > 1: # Set up the multiprocessing @@ -172,7 +181,7 @@ def get_bagle_model_list(event_table, comp_table, lcurve_table, return all_models -def get_bagle_model(event, companions, photometric_system, filter_name, red_law): +def get_bagle_model(event, companions, photometric_system, filter_name, red_law, return_name_and_dict = False): """ Get a BAGLE model instance for a single event (and its associated companions). @@ -194,8 +203,20 @@ def get_bagle_model(event, companions, photometric_system, filter_name, red_law) red_law : str + return_name_and_dict : bool, Optional + If True, returns name of model and parameter dict instead of model. + Default is False. + Returns ------- + mod : BAGLE model object + Returns by default + + model_name : str + Name of BAGLE model, returned if return_name_and_dict = True + + parameter_dict : dict + Dictionary of BAGLE model parameters, returned if return_name_and_dict = True """ event = event[0] @@ -203,11 +224,405 @@ def get_bagle_model(event, companions, photometric_system, filter_name, red_law) model_name, parameter_dict = get_bagle_model_name_and_params(event, companions, photometric_system, filter_name, red_law) + if return_name_and_dict: + return model_name, parameter_dict + mod_class = getattr(model, model_name) mod = mod_class(**parameter_dict) return mod +def coords_and_prop_motion(event): + # Get the coordinates of this event. + L_coords = SkyCoord(l=event['glon_L'] * unit.degree, + b=event['glat_L'] * unit.degree, + pm_l_cosb=event['mu_lcosb_L'] * unit.mas / unit.year, + pm_b=event['mu_b_L'] * unit.mas / unit.year, frame='galactic') + S_coords = SkyCoord(l=event['glon_S'] * unit.degree, + b=event['glat_S'] * unit.degree, + pm_l_cosb=event['mu_lcosb_S'] * unit.mas / unit.year, + pm_b=event['mu_b_S'] * unit.mas / unit.year, frame='galactic') + + raL = L_coords.icrs.ra.value # Lens R.A. + decL = L_coords.icrs.dec.value # Lens dec + muL = np.array([L_coords.icrs.pm_ra_cosdec.value, L_coords.icrs.pm_dec.value]) # lens proper motion mas/year + muS = np.array([S_coords.icrs.pm_ra_cosdec.value, S_coords.icrs.pm_dec.value]) # source proper motion mas/year + + return raL, decL, muL, muS + +def get_pspl_lightcurve_parameters(events, photometric_system, filter_name, event_id = None): + """ + Find the parameters for PSPL_PhotAstrom_Par_Param1 from + event_table. + + Parameters + ---------- + events : Astropy table + Table containing the events calculated from refine_events. + + photometric_system : str + The name of the photometric system in which the filter exists. + + filter_name : str + The name of the filter in which to calculate all the + microlensing events. The filter name convention is set + in the global filt_dict parameter at the top of this module. + + event_id : float or None, optional + Index of event table of event. If len(events) > 1, this must be specified. + Default is None. + + Returns + ------- + parameter_dict : dict + Dictionary of the PSPL_PhotAstrom_Par_Param1 parameters + + obj_id_L : int + Object id of the lens associated with event + + obj_id_S : int + Object id of the source associated with event + + model_name : str + Name of model associated with event. + """ + + if len(events) == 1 or type(events) == table.row.Row: + event = events + else: + if event_id == None: + raise Exception('If you input more than one event, must specify event id') + else: + event = events[event_id] + + obj_id_L = event['obj_id_L'] + obj_id_S = event['obj_id_S'] + + raL, decL, muL, muS = coords_and_prop_motion(event) + + model_name = 'PSPL_PhotAstrom_Par_Param1' + + mL = event['mass_L'] # Msun (Primary lens current mass) + t0 = event['t0'] # mjd + xS0 = np.array([0, 0]) # arbitrary offset (arcsec) + beta = event['u0'] * event['theta_E'] # mas + dL = event['rad_L'] * 10 ** 3 # Distance to lens + dS = event['rad_S'] * 10 ** 3 # Distance to source + mag_src = [event['%s_%s_app_S' % (photometric_system, filter_name)]] + b_sff = [event['f_blend_%s' % filter_name]] + + parameter_dict = {'raL': raL, 'decL': decL, 'mL': mL, + 't0': t0, 'beta': beta, 'dL': dL, 'dL_dS': dL / dS, + 'xS0_E': xS0[0], 'xS0_N': xS0[1], + 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], + 'b_sff': b_sff, 'mag_src': mag_src} + + return parameter_dict, obj_id_L, obj_id_S, model_name + +def get_psbl_lightcurve_parameters(events, companions, comp_idx_L, photometric_system, filter_name, event_id = None): + """ + Find the parameters for PSBL_PhotAstrom_Par_EllOrbs_Param7 from + event_table and comp_table. + + Parameters + ---------- + events : Astropy table + Table containing the events calculated from refine_events. + + companions : Astropy table + Table containing the companions calculated from refine_events. + + comp_idx_L : int + Index into the comp_table of the companion for which the psbl is being calculated. + + photometric_system : str + The name of the photometric system in which the filter exists. + + filter_name : str + The name of the filter in which to calculate all the + microlensing events. The filter name convention is set + in the global filt_dict parameter at the top of this module. + + event_id : float or None, optional + Corresponding event_id in event_table to companion id. + Default is None. + + Returns + ------- + parameter_dict : dict + Dictionary of the PSBL_PhotAstrom_EllOrbs_Par_Param7 parameters + + obj_id_L : int + Object id of the lens associated with event + + obj_id_S : int + Object id of the source associated with event + + model_name : str + Name of model associated with event. + + """ + if (type(comp_idx_L) != int) and (type(comp_idx_L) != np.int64): + raise Exception('comp_idx_L must be an integer') + + if type(events) == table.row.Row: + event = events + obj_id_L = event['obj_id_L'] + obj_id_S = event['obj_id_S'] + else: + if event_id == None: + obj_id_L = companions['obj_id_L'][comp_idx_L] + obj_id_S = companions['obj_id_S'][comp_idx_S] + event_id = (np.where(np.logical_and((events['obj_id_L'] == obj_id_L), (events['obj_id_S'] == obj_id_S)))[0])[0] + event = events[event_id] + else: + event = events[event_id] + obj_id_L = event['obj_id_L'] + obj_id_S = event['obj_id_S'] + + model_name = 'PSBL_PhotAstrom_Par_EllOrbs_Param7' + + raL, decL, muL, muS = coords_and_prop_motion(event) + + mLp = event['mass_L'] # msun (Primary lens current mass) + mLs = companions['mass'][comp_idx_L] # msun (Companion lens current mass) + t0_p = event['t0'] # mjd + xS0 = np.array([0, 0]) # arbitrary offset (arcsec) + beta_p = event['u0'] * event['theta_E'] # 5.0 + dL = event['rad_L'] * 10 ** 3 # Distance to lens + dS = event['rad_S'] * 10 ** 3 # Distance to source + mag_src = [event['%s_%s_app_S' % (photometric_system, filter_name)]] + b_sff = [event['f_blend_%s' % filter_name]] # ASSUMES ALL BINARY LENSES ARE BLENDED + omega = companions['omega'][comp_idx_L] + big_omega = companions['Omega'][comp_idx_L] + i = companions['i'][comp_idx_L] + e = companions['e'][comp_idx_L] + tp = companions['tp'][comp_idx_L] + a = 10**(companions['log_a'][comp_idx_L]) + dmag_Lp_Ls = [event['%s_%s_L' % (photometric_system, filter_name)] - companions['m_%s_%s' % (photometric_system, filter_name)][comp_idx_L]] + + + parameter_dict = {'raL': raL, 'decL': decL, + 'mLp': mLp, 'mLs': mLs, 't0_p': t0_p, + 'xS0_E': xS0[0], 'xS0_N': xS0[1], 'beta_p': beta_p, + 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], + 'dL': dL, 'dS': dS, + #'sep': sep, 'alpha': alpha, + 'mag_src': mag_src, 'b_sff': b_sff, 'omega_pri': omega, 'big_omega_sec': big_omega, + 'i': i, 'e': e, 'tp': tp, 'a': a, 'dmag_Lp_Ls': dmag_Lp_Ls} + + return parameter_dict, obj_id_L, obj_id_S, model_name + +def get_bspl_lightcurve_parameters(events, companions, comp_idx_S, photometric_system, filter_name, red_law, event_id = None): + """ + Find the parameters for BSPL_PhotAstrom_Par_EllOrbs_Param4 from + event_table and comp_table. + + Parameters + ---------- + events : Astropy table + Table containing the events calculated from refine_events. + + companions : Astropy table + Table containing the companions calculated from refine_events. + + comp_idx_S : int + Index into the comp_table of the companion for which the bspl is being calculated. + + photometric_system : str + The name of the photometric system in which the filter exists. + + filter_name : str + The name of the filter in which to calculate all the + microlensing events. The filter name convention is set + in the global filt_dict parameter at the top of this module. + + red_law : str + Redenning law + + event_id : float or None, optional + Corresponding event_id in event_table to companion id + + Returns + ------- + parameter_dict : dict + Dictionary of the BSPL_PhotAstrom_Par_EllOrbs_Param4 parameters + + obj_id_L : int + Object id of the lens associated with event + + obj_id_S : int + Object id of the source associated with event + + model_name : str + Name of model associated with event. + """ + if (type(comp_idx_S) != int) and (type(comp_idx_S) != np.int64): + raise Exception('comp_idx_S must be an integer') + + if type(events) == table.row.Row: + event = events + obj_id_L = event['obj_id_L'] + obj_id_S = event['obj_id_S'] + else: + if event_id == None: + obj_id_L = companions['obj_id_L'][comp_idx_L] + obj_id_S = companions['obj_id_S'][comp_idx_S] + event_id = (np.where(np.logical_and((events['obj_id_L'] == obj_id_L), (events['obj_id_S'] == obj_id_S)))[0])[0] + event = events[event_id] + else: + event = events[event_id] + obj_id_L = event['obj_id_L'] + obj_id_S = event['obj_id_S'] + + raL, decL, muL, muS = coords_and_prop_motion(event) + + model_name = 'BSPL_PhotAstrom_Par_EllOrbs_Param4' + + filt_dict = phot_utils.make_filt_dict() + f_i = filt_dict[photometric_system + '_' + filter_name][red_law] + abs_mag_sec = companions['m_%s_%s' % (photometric_system, filter_name)][comp_idx_S] + + mL = event['mass_L'] # msun (Lens current mass) + t0_p = event['t0'] # mjd + beta_p = event['u0'] * event['theta_E'] # 5.0 + dL = event['rad_L'] * 10 ** 3 # Distance to lens + dL_dS = dL / (event['rad_S'] * 10 ** 3) # Distance to lens/Distance to source + xS0 = np.array([0, 0]) # arbitrary offset (arcsec) + mag_src_sec = synthetic.calc_app_mag(event['rad_S'], abs_mag_sec, event['exbv_S'], f_i) + mag_src_pri = binary_utils.subtract_magnitudes( + event['%s_%s_app_S' % (photometric_system, filter_name)], mag_src_sec) + b_sff = event['f_blend_%s' % filter_name] # ASSUMES THAT SOURCE BINARIES ARE BLENDED + omega = companions['omega'][comp_idx_S] + big_omega = companions['Omega'][comp_idx_S] + i = companions['i'][comp_idx_S] + e = companions['e'][comp_idx_S] + log_a = companions['log_a'][comp_idx_S] + tp = companions['tp'][comp_idx_S] + mass_source_p = event['mass_S'] + mass_source_s = companions['mass'][comp_idx_S] + + parameter_dict = {'raL': raL, 'decL': decL, 'mL': mL, + 't0': t0_p, 'beta': beta_p, 'dL': dL, 'dL_dS': dL_dS, + 'xS0_E': xS0[0], 'xS0_N': xS0[1], + 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], + 'mag_src_pri': [mag_src_pri], 'mag_src_sec': [mag_src_sec], 'b_sff': [b_sff], 'omega_pri': omega, 'big_omega_sec': big_omega, + 'i': i, 'e': e, 'log_a': log_a, 'mass_source_p': mass_source_p, 'mass_source_s': mass_source_s, 'tp': tp} + + return parameter_dict, obj_id_L, obj_id_S, model_name + +def get_bsbl_lightcurve_parameters(events, companions, comp_idx_L, comp_idx_S, photometric_system, filter_name, red_law, event_id = None): + """ + Find the parameters for BSBL_PhotAstrom_Par_EllOrbs_Param3 from + event_table and comp_table. + + Parameters + ---------- + events : Astropy table + Table containing the events calculated from refine_events. + + companions : Astropy table + Table containing the companions calculated from refine_events. + + comp_idx_L : int + Index into the comp_table of the lens companion for which the model is being calculated. + + comp_idx_S : int + Index into the comp_table of the source companion for which the model is being calculated. + + photometric_system : str + The name of the photometric system in which the filter exists. + + filter_name : str + The name of the filter in which to calculate all the + microlensing events. The filter name convention is set + in the global filt_dict parameter at the top of this module. + + red_law : str + Redenning law + + event_id : float or None, optional + Corresponding event_id in event_table to companion id. + Default is None. + + Returns + ------- + parameter_dict : dict + Dictionary of the BSBL_PhotAstrom_Par_EllOrbs_Param3 parameters + + obj_id_L : int + Object id of the lens associated with event + + obj_id_S : int + Object id of the source associated with event + + model_name : str + Name of model associated with event. + """ + if ((type(comp_idx_L) != int) and (type(comp_idx_L) != np.int64)) or ((type(comp_idx_S) != int) and (type(comp_idx_S) != np.int64)): + raise Exception('comp_idx_L and comp_idx_S must be integers') + + if type(events) == table.row.Row: + event = events + obj_id_L = event['obj_id_L'] + obj_id_S = event['obj_id_S'] + else: + if event_id == None: + obj_id_L = companions['obj_id_L'][comp_idx_L] + obj_id_S = companions['obj_id_S'][comp_idx_S] + event_id = (np.where(np.logical_and((events['obj_id_L'] == obj_id_L), (events['obj_id_S'] == obj_id_S)))[0])[0] + event = events[event_id] + else: + event = events[event_id] + obj_id_L = event['obj_id_L'] + obj_id_S = event['obj_id_S'] + + raL, decL, muL, muS = coords_and_prop_motion(event) + + model_name = 'BSBL_PhotAstrom_Par_EllOrbs_Param3' + + f_i = synthetic.filt_dict[photometric_system + '_' + filter_name][red_law] + abs_mag_sec = companions['m_%s_%s' % (photometric_system, filter_name)][comp_idx_S] + + mLp = event['mass_L'] # msun (Lens current mass) + mLs = companions['mass'][comp_idx_L] # msun (Companion lens current mass) + t0_p = event['t0'] # mjd + beta_p = event['u0'] * event['theta_E'] # 5.0 + dL = event['rad_L'] * 10 ** 3 # Distance to lens + dS = event['rad_S'] * 10 ** 3 # Distance to source + xS0_E = 0.0 # arbitrary offset (arcsec) + xS0_N = 0.0 # arbitrary offset (arcsec) + mag_src_sec = synthetic.calc_app_mag(event['rad_S'], abs_mag_sec, event['exbv_S'], f_i) + mag_src_pri = binary_utils.subtract_magnitudes( + event['%s_%s_app_S' % (photometric_system, filter_name)], mag_src_sec) + b_sff = event['f_blend_%s' % filter_name] # ASSUMES THAT SOURCE BINARIES ARE BLENDED + omegaL = companions['omega'][comp_idx_L] + big_omegaL = companions['Omega'][comp_idx_L] + iL = companions['i'][comp_idx_L] + eL = companions['e'][comp_idx_L] + tpL = companions['tp'][comp_idx_L] + aL = 10**(companions['log_a'][comp_idx_L]) + omegaS = companions['omega'][comp_idx_S] + big_omegaS = companions['Omega'][comp_idx_S] + iS = companions['i'][comp_idx_S] + eS = companions['e'][comp_idx_S] + tpS = companions['tp'][comp_idx_S] + aS = 10**(companions['log_a'][comp_idx_S]) + dmag_Lp_Ls = [event['%s_%s_L' % (photometric_system, filter_name)] - companions['m_%s_%s' % (photometric_system, filter_name)][comp_idx_L]] + mass_source_p = event['mass_S'] + mass_source_s = companions['mass'][comp_idx_S] + + parameter_dict = {'raL': raL, 'decL': decL, 'mLp': mLp, 'mLs': mLs, + 't0_p': t0_p, 'xS0_E': xS0_E, 'xS0_N': xS0_N, 'beta_p': beta_p, + 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], + 'dL': dL, 'dS': dS, + 'mag_src_pri': [mag_src_pri], 'mag_src_sec': [mag_src_sec], 'b_sff': [b_sff], + 'omegaL_pri': omegaL, 'big_omegaL_sec': big_omegaL, 'iL': iL, 'eL': eL, 'tpL': tpL, 'aL': aL, + 'omegaS_pri': omegaS, 'big_omegaS_sec': big_omegaS, 'iS': iS, 'eS': eS, 'tpS': tpS, 'aS': aS, + 'dmag_Lp_Ls': dmag_Lp_Ls, 'mass_source_p': mass_source_p, 'mass_source_s': mass_source_s} + + + return parameter_dict, obj_id_L, obj_id_S, model_name def get_bagle_model_name_and_params(event, companions, photometric_system, filter_name, red_law): """ @@ -253,46 +668,10 @@ def get_bagle_model_name_and_params(event, companions, photometric_system, filte else: event_type = 'PSPL' - # Get the lens and source ID numbers. - obj_id_L = event['obj_id_L'] - obj_id_S = event['obj_id_S'] - - # Get the coordinates of this event. - L_coords = SkyCoord(l=event['glon_L'] * unit.degree, - b=event['glat_L'] * unit.degree, - pm_l_cosb=event['mu_lcosb_L'] * unit.mas / unit.year, - pm_b=event['mu_b_L'] * unit.mas / unit.year, frame='galactic') - S_coords = SkyCoord(l=event['glon_S'] * unit.degree, - b=event['glat_S'] * unit.degree, - pm_l_cosb=event['mu_lcosb_S'] * unit.mas / unit.year, - pm_b=event['mu_b_S'] * unit.mas / unit.year, frame='galactic') - - raL = L_coords.icrs.ra.value # Lens R.A. - decL = L_coords.icrs.dec.value # Lens dec - muL = np.array([L_coords.icrs.pm_ra_cosdec.value, L_coords.icrs.pm_dec.value]) # lens proper motion mas/year - muS = np.array([S_coords.icrs.pm_ra_cosdec.value, S_coords.icrs.pm_dec.value]) # source proper motion mas/year - if event_type == 'PSPL': - model_name = 'PSPL_PhotAstrom_Par_Param1' - - mL = event['mass_L'] # Msun (Primary lens current mass) - t0 = event['t0'] # mjd - xS0 = np.array([0, 0]) # arbitrary offset (arcsec) - beta = event['u0'] * event['theta_E'] # mas - dL = event['rad_L'] * 10 ** 3 # Distance to lens - dS = event['rad_S'] * 10 ** 3 # Distance to source - mag_src = [event['%s_%s_app_S' % (photometric_system, filter_name)]] - b_sff = [event['f_blend_%s' % filter_name]] - - parameter_dict = {'raL': raL, 'decL': decL, 'mL': mL, - 't0': t0, 'beta': beta, 'dL': dL, 'dL_dS': dL / dS, - 'xS0_E': xS0[0], 'xS0_N': xS0[1], - 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], - 'b_sff': b_sff, 'mag_src': mag_src} + parameter_dict, obj_id_L, obj_id_S, model_name = get_pspl_lightcurve_parameters(event, photometric_system, filter_name) if event_type == 'PSBL': - model_name = 'PSBL_PhotAstrom_Par_Param7' - # There should only be a single lens companion. comp_idxs_L = np.where(companions['prim_type'] == b"L")[0] @@ -301,62 +680,19 @@ def get_bagle_model_name_and_params(event, companions, photometric_system, filte else: comp_idx_L = comp_idxs_L[0] - mLp = event['mass_L'] # msun (Primary lens current mass) - mLs = companions['mass'][comp_idx_L] # msun (Companion lens current mass) - t0_p = event['t0'] # mjd - xS0 = np.array([0, 0]) # arbitrary offset (arcsec) - beta_p = event['u0'] * event['theta_E'] # 5.0 - dL = event['rad_L'] * 10 ** 3 # Distance to lens - dS = event['rad_S'] * 10 ** 3 # Distance to source - sep = companions['sep'][comp_idx_L] # mas (separation between primary and companion) - alpha = companions['alpha'][comp_idx_L] - mag_src = [event['%s_%s_app_S' % (photometric_system, filter_name)]] - b_sff = [event['f_blend_%s' % filter_name]] # ASSUMES ALL BINARY LENSES ARE BLENDED - - parameter_dict = {'raL': raL, 'decL': decL, - 'mLp': mLp, 'mLs': mLs, 't0_p': t0_p, - 'xS0_E': xS0[0], 'xS0_N': xS0[1], 'beta_p': beta_p, - 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], - 'dL': dL, 'dS': dS, 'sep': sep, - 'alpha': alpha, 'mag_src': mag_src, 'b_sff': b_sff} + parameter_dict, obj_id_L, obj_id_S, model_name = get_psbl_lightcurve_parameters(event, companions, comp_idx_L, photometric_system, filter_name) if event_type == 'BSPL': - model_name = 'BSPL_PhotAstrom_Par_Param1' - - # There should only be a single source companion. comp_idxs_S = np.where(companions['prim_type'] == b"S")[0] - + if len(comp_idxs_S) > 1: raise RuntimeError('Found too many source companions. Triples not supported.') else: comp_idx_S = comp_idxs_S[0] - f_i = synthetic.filt_dict[photometric_system + '_' + filter_name][red_law] - abs_mag_sec = companions['m_%s_%s' % (photometric_system, filter_name)][comp_idx_S] - - mL = event['mass_L'] # msun (Lens current mass) - t0_p = event['t0'] # mjd - beta_p = event['u0'] * event['theta_E'] # 5.0 - dL = event['rad_L'] * 10 ** 3 # Distance to lens - dL_dS = dL / (event['rad_S'] * 10 ** 3) # Distance to lens/Distance to source - xS0 = np.array([0, 0]) # arbitrary offset (arcsec) - sep = companions['sep'][comp_idx_S] # mas (separation between primary and companion) - alpha = companions['alpha'][comp_idx_S] - mag_src_sec = [synthetic.calc_app_mag(event['rad_S'], abs_mag_sec, event['exbv_S'], f_i)] - mag_src_pri = [binary_utils.subtract_magnitudes( - event['%s_%s_app_S' % (photometric_system, filter_name)], mag_src_sec)] - b_sff = [event['f_blend_%s' % filter_name]] # ASSUMES THAT SOURCE BINARIES ARE BLENDED - - parameter_dict = {'raL': raL, 'decL': decL, 'mL': mL, - 't0': t0_p, 'beta': beta_p, 'dL': dL, 'dL_dS': dL_dS, - 'xS0_E': xS0[0], 'xS0_N': xS0[1], - 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], - 'sep': sep, 'alpha': alpha, - 'mag_src_pri': mag_src_pri, 'mag_src_sec': mag_src_sec, 'b_sff': b_sff} + parameter_dict, obj_id_L, obj_id_S, model_name = get_bspl_lightcurve_parameters(event, companions, comp_idx_S, photometric_system, filter_name, red_law) if event_type == 'BSBL': - model_name = 'BSBL_PhotAstrom_Par_Param2' - # There should only be a single source companion. comp_idxs_S = np.where(companions['prim_type'] == b"S")[0] comp_idxs_L = np.where(companions['prim_type'] == b"L")[0] @@ -371,32 +707,6 @@ def get_bagle_model_name_and_params(event, companions, photometric_system, filte else: comp_idx_L = comp_idxs_L[0] - f_i = synthetic.filt_dict[photometric_system + '_' + filter_name][red_law] - abs_mag_sec = companions['m_%s_%s' % (photometric_system, filter_name)][comp_idx_S] - - mLp = event['mass_L'] # msun (Lens current mass) - mLs = companions['mass'][comp_idx_L] # msun (Companion lens current mass) - t0_p = event['t0'] # mjd - beta_p = event['u0'] * event['theta_E'] # 5.0 - dL = event['rad_L'] * 10 ** 3 # Distance to lens - dS = event['rad_S'] * 10 ** 3 # Distance to source - xS0_E = 0.0 # arbitrary offset (arcsec) - xS0_N = 0.0 # arbitrary offset (arcsec) - sepL = companions['sep'][comp_idx_L] # mas (separation between primary and companion) - alphaL = companions['alpha'][comp_idx_L] # PA of binary on the sky - sepS = companions['sep'][comp_idx_S] # mas (separation between primary and companion) - alphaS = companions['alpha'][comp_idx_S] # PA of source binary on the sky - mag_src_sec = [synthetic.calc_app_mag(event['rad_S'], abs_mag_sec, event['exbv_S'], f_i)] - mag_src_pri = [binary_utils.subtract_magnitudes( - event['%s_%s_app_S' % (photometric_system, filter_name)], mag_src_sec)] - b_sff = [event['f_blend_%s' % filter_name]] # ASSUMES THAT SOURCE BINARIES ARE BLENDED - - parameter_dict = {'raL': raL, 'decL': decL, 'mLp': mLp, 'mLs': mLs, - 't0_p': t0_p, 'xS0_E': xS0_E, 'xS0_N': xS0_N, 'beta_p': beta_p, - 'muL_E': muL[0], 'muL_N': muL[1], 'muS_E': muS[0], 'muS_N': muS[1], - 'dL': dL, 'dS': dS, 'sepL': sepL, 'alphaL': alphaL, - 'sepS': sepS, 'alphaS': alphaS, - 'mag_src_pri': mag_src_pri, 'mag_src_sec': mag_src_sec, - 'b_sff': b_sff} + parameter_dict, obj_id_L, obj_id_S, model_name = get_bsbl_lightcurve_parameters(event, companions, comp_idx_L, comp_idx_S, photometric_system, filter_name, red_law) return model_name, parameter_dict diff --git a/popsycle/phot_utils.py b/popsycle/phot_utils.py new file mode 100755 index 0000000..a60adf0 --- /dev/null +++ b/popsycle/phot_utils.py @@ -0,0 +1,66 @@ +def make_filt_dict(): + """ + Dictionary for extinction law coefficients f_i, as a function of filter + + Damineli values from photometric bands (nm): + B = 445, V = 551, I = 806, J = 1220, H = 1630, K = 2190, U = 365, R = 658 + + SDSS photometric bands https://skyserver.sdss.org/dr1/en/proj/advanced/color/sdssfilters.asp (nm): + u = 354.3, g = 477.0, r = 623.1, i = 762.5, z = 913.4 + + ZTF photometric bands: + G = 472.274, R = 633.961, I = 788.613 + + RUBIN photometric bands https://github.com/lsst-pst/syseng_throughputs (nm): + u = 372.0, g = 480.3, r = 622.0, i = 755.8, z = 868.0, y = 974.9 + + Schlegel and Schlafly photometric bands: + B = 440, V = 543, I = 809, J = 1266, H = 1673, K = 2215, U = 337, R = 651 + Calculated using calc_f + """ + filt_dict = {} + filt_dict['ubv_J'] = {'Schlafly11': 0.709, 'Schlegel99': 0.902, 'Damineli16': 0.662} + filt_dict['ubv_H'] = {'Schlafly11': 0.449, 'Schlegel99': 0.576, 'Damineli16': 0.344} + filt_dict['ubv_K'] = {'Schlafly11': 0.302, 'Schlegel99': 0.367, 'Damineli16': 0.172} + filt_dict['ubv_U'] = {'Schlafly11': 4.334, 'Schlegel99': 5.434, 'Damineli16': 5.022} + filt_dict['ubv_B'] = {'Schlafly11': 3.626, 'Schlegel99': 4.315, 'Damineli16': 3.757} + filt_dict['ubv_V'] = {'Schlafly11': 2.742, 'Schlegel99': 3.315, 'Damineli16': 2.757} + filt_dict['ubv_I'] = {'Schlafly11': 1.505, 'Schlegel99': 1.940, 'Damineli16': 1.496} + filt_dict['ubv_R'] = {'Schlafly11': 2.169, 'Schlegel99': 2.634, 'Damineli16': 2.102} + filt_dict['ztf_g'] = {'Damineli16': 3.453} + filt_dict['ztf_r'] = {'Damineli16': 2.228} + filt_dict['ztf_i'] = {'Damineli16': 1.553} + filt_dict['sdss_u'] = {'Damineli16': 5.262} + filt_dict['sdss_g'] = {'Damineli16': 3.401} + filt_dict['sdss_r'] = {'Damineli16': 2.290} + filt_dict['sdss_i'] = {'Damineli16': 1.650} + filt_dict['sdss_z'] = {'Damineli16': 1.192} + filt_dict['rubin_u'] = {'Damineli16': 4.880} + filt_dict['rubin_g'] = {'Damineli16': 3.370} + filt_dict['rubin_r'] = {'Damineli16': 2.296} + filt_dict['rubin_i'] = {'Damineli16': 1.672} + filt_dict['rubin_z'] = {'Damineli16': 1.309} + filt_dict['rubin_y'] = {'Damineli16': 1.051} + filt_dict['roman_f062'] = {'Damineli16': 2.307} + filt_dict['roman_f087'] = {'Damineli16': 1.307} + filt_dict['roman_f106'] = {'Damineli16': 0.889} + filt_dict['roman_f129'] = {'Damineli16': 0.583} + filt_dict['roman_f158'] = {'Damineli16': 0.372} + filt_dict['roman_f146'] = {'Damineli16': 0.441} #same as w146 + filt_dict['roman_f184'] = {'Damineli16': 0.258} + filt_dict['roman_f213'] = {'Damineli16': 0.184} + + return filt_dict + +def make_photometric_system_dict(): + """ + Dictionary for listing out supported photometric systems and filters + """ + photometric_system_dict = {} + photometric_system_dict['ubv'] = ['J', 'H', 'K', 'U', 'B', 'V', 'I', 'R'] + photometric_system_dict['ztf'] = ['g', 'r', 'i'] + photometric_system_dict['sdss'] = ['u', 'g', 'r', 'i', 'z'] + photometric_system_dict['rubin'] = ['u','g','r','i','z','y'] + photometric_system_dict['roman'] = ['f062','f087','f106','f129','f158','f146','f184','f213'] #f146 same as w146 + + return photometric_system_dict \ No newline at end of file diff --git a/popsycle/run.py b/popsycle/run.py index 527f091..0ce5816 100755 --- a/popsycle/run.py +++ b/popsycle/run.py @@ -20,9 +20,11 @@ from popsycle.synthetic import _check_refine_events from popsycle.synthetic import _check_refine_binary_events from popsycle.synthetic import multiplicity_list +from popsycle import binary_utils +from popsycle import phot_utils -def _return_filename_dict(output_root): +def _return_filename_dict(output_root, filter_dict, red_law, multiplicity = None): """ Return the filenames of the files output by the pipeline @@ -34,6 +36,19 @@ def _return_filename_dict(output_root): '{output_root}.h5' '{output_root}.ebf' '{output_root}_events.h5' + + filter_dict : dict + Dictionary with desired photometric systems and filters to calculate microlensing events for. + The dictionary keys are photometric systems. + The dictionary values are lists of strings filled with filters within that photometric system key. + Example: + To calculate the events for UBV U, and ZTF, u, g, r: + filter_dict = {'ubv':['U'],'ztf':['u','g','r']} + + multiplicity : None or object, optional + Multiplicity object is either None or the multiplicity object. + If it's not none, an hdf5 and fits companion filename will be added. + Default is None. Returns ------- @@ -48,15 +63,43 @@ def _return_filename_dict(output_root): blends_filename = '%s_blends.fits' % output_root noevents_filename = '%s_NOEVENTS.txt' % output_root + photometric_system = list(filter_dict.keys())[0] + filter_name = filter_dict[photometric_system][0] + refined_binary_events_filename = '{0:s}_refined_binary_events_' \ + '{1:s}_{2:s}_{3:s}.' \ + 'fits'.format(output_root, + photometric_system, + filter_name, + red_law) + # refined_events_filename defaults to using multi_filt, + # unless filter_dict contains only one filter + if (len(filter_dict)==1) & (len(filter_dict[photometric_system]) == 1): + refined_events_filename = '{0:s}_refined_events_' \ + '{1:s}_{2:s}_{3:s}.' \ + 'fits'.format(output_root, + photometric_system, + filter_name, + red_law) + else: + refined_events_filename = '{0:s}_refined_events_' \ + 'multi_filt_{1:s}.' \ + 'fits'.format(output_root, + red_law) + # Add the filenames to a dictionary filename_dict = { 'ebf_filename': ebf_filename, 'hdf5_filename': hdf5_filename, 'events_filename': events_filename, 'blends_filename': blends_filename, - 'noevents_filename': noevents_filename + 'noevents_filename': noevents_filename, + 'refined_events_filename': refined_events_filename, + 'refined_binary_events_filename': refined_binary_events_filename } + if multiplicity is not None: + filename_dict['hdf5_companions_filename'] = hdf5_filename[:-3] + '_companions.h5' + filename_dict['companions_filename'] = events_filename[:-11] + 'companions.fits' return filename_dict @@ -194,9 +237,10 @@ def generate_popsycle_config_file(radius_cut=2, obs_time=1000, bin_edges_number=None, BH_kick_speed_mean=50, NS_kick_speed_mean=400, - photometric_system='ubv', - filter_name='R', red_law='Damineli16', + filter_dict={'ubv':['R']}, + red_law='Damineli16', multiplicity=None, + bbh_frac = 'default', binning = True, config_filename='popsycle_config.yaml'): """ @@ -251,13 +295,16 @@ def generate_popsycle_config_file(radius_cut=2, obs_time=1000, Hobbs et al 2005 'A statistical study of 233 pulsar proper motions'. https://ui.adsabs.harvard.edu/abs/2005MNRAS.360..974H/abstract - photometric_system : str - The name of the photometric system in which the filter exists. - - filter_name : str - The name of the filter in which to calculate all the - microlensing events. The filter name convention is set - in the global filt_dict parameter at the top of this module. + filter_dict : dict + Dictionary with desired photometric systems and filters to calculate microlensing events for. + The dictionary keys are strings of photometric systems. + The dictionary values are lists of strings filled with filters within that photometric system key. + Example: + To calculate the events for UBV U, and ZTF, u, g, r: + filter_dict = {'ubv':['U'],'ztf':['u','g','r']} + NOTE: photometric_system and filter_name are deprecated, please use filter_dict. + NOTE: When running refine_binary_events, the first dictionary key and value are used. + The above example would use UBV U in refine_binary_events. red_law : str The name of the reddening law to use from SPISEA. @@ -266,6 +313,11 @@ def generate_popsycle_config_file(radius_cut=2, obs_time=1000, If a resovled multiplicity object is specified, the table will be generated with resolved multiples. Default is None. + + bbh_frac : str or float + If make_bhs_single() is run, this is the fraction of binary black holes. + If bbh_frac = 'default', then make_bhs_single() will not be run. + Default is 'default'. binning : bool If set to True, bins files as specified by bin_edges_numbers or default. @@ -302,10 +354,10 @@ def generate_popsycle_config_file(radius_cut=2, obs_time=1000, 'bin_edges_number': bin_edges_number, 'BH_kick_speed_mean': BH_kick_speed_mean, 'NS_kick_speed_mean': NS_kick_speed_mean, - 'photometric_system': photometric_system, - 'filter_name': filter_name, + 'filter_dict': filter_dict, 'red_law': red_law, 'multiplicity': multiplicity, + 'bbh_frac' : bbh_frac, 'binning':binning} generate_config_file(config_filename, config) @@ -440,6 +492,7 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, seed=None, overwrite=False, submitFlag=True, returnJobID=True, dependencyJobID=None, skip_galaxia=False, skip_perform_pop_syn=False, + skip_make_bhs_single = True, skip_calc_events=False, skip_refine_events=False, skip_refine_binary_events=False, verbose = 0): @@ -542,6 +595,11 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, the resulting h5 file is already present. Default is False + skip_make_bhs_single : bool, optional + If True, pipeline will not run make_bhs_single and the default + black hole binary fraction from perform_pop_syn will be kept. + Default is True + skip_calc_events : bool, optional If True, pipeline will not run calc_events and assume that the resulting events and blends files are already present. @@ -609,6 +667,10 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, skip_refine_binary_events = True hdf5_file_comp = None + # Dont run make_bhs_single() if the bbh_frac is left default + if popsycle_config['bbh_frac'] == 'default': + skip_make_bhs_single = True + # Load the slurm configuration file slurm_config = load_config_file(slurm_config_filename) @@ -616,6 +678,17 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, n_cores_popsycle = {'n_cores_perform_pop_syn' : n_cores_perform_pop_syn, 'n_cores_calc_events' : n_cores_calc_events, 'n_cores_refine_binary_events' : n_cores_refine_binary_events} + + # Prepare additional_photometric_systems + additional_photometric_systems = [] + for photometric_system in popsycle_config['filter_dict']: + if photometric_system != 'ubv': + additional_photometric_systems += [photometric_system] + if additional_photometric_systems == []: + additional_photometric_systems = None + + # Return the dictionary containing PopSyCLE output filenames + filename_dict = _return_filename_dict(output_root, popsycle_config['filter_dict'], popsycle_config['red_law'], multiplicity) # Check pipeline stages for valid inputs _check_slurm_config(slurm_config, walltime) @@ -636,7 +709,7 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, bin_edges_number=popsycle_config['bin_edges_number'], BH_kick_speed_mean=popsycle_config['BH_kick_speed_mean'], NS_kick_speed_mean=popsycle_config['NS_kick_speed_mean'], - additional_photometric_systems=[popsycle_config['photometric_system']], + additional_photometric_systems=additional_photometric_systems, n_proc=n_cores_perform_pop_syn, binning = popsycle_config['binning'], verbose = verbose, @@ -656,8 +729,7 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, hdf5_file_comp=hdf5_file_comp) if not skip_refine_events: _check_refine_events(input_root='test', - filter_name=popsycle_config['filter_name'], - photometric_system=popsycle_config['photometric_system'], + filter_dict=popsycle_config['filter_dict'], red_law=popsycle_config['red_law'], overwrite=overwrite, legacy=False, @@ -665,18 +737,16 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, hdf5_file_comp=hdf5_file_comp, seed=seed) if not skip_refine_binary_events: - refined_events_filename = '{0:s}_refined_events_' \ - '{1:s}_{2:s}_{3:s}.' \ - 'fits'.format(output_root, - popsycle_config['photometric_system'], - popsycle_config['filter_name'], - popsycle_config['red_law']) - refined_events_comp_filename = refined_events_filename.replace('.fits', '_companions.fits') + # refined_events_filename defaults to using multi_filt, + # unless popsycle_config['filter_dict'] contains only one filter + refined_events_comp_filename = filename_dict['refined_events_filename'].replace('.fits', '_companions.fits') phot_dir = '%s_bin_phot' % output_root - _check_refine_binary_events(events=refined_events_filename, + photmetric_system = list(popsycle_config['filter_dict'])[0] + filter_name = popsycle_config['filter_dict'][photometric_system][0] + _check_refine_binary_events(events=filename_dict['refined_events_filename'], companions=refined_events_comp_filename, - photometric_system=popsycle_config['photometric_system'], - filter_name=popsycle_config['filter_name'], + filter_name=filter_name, + photometric_system=photometric_system, n_proc=n_cores_refine_binary_events, overwrite=overwrite, output_file='default', save_phot=True, @@ -811,6 +881,9 @@ def generate_slurm_script(slurm_config_filename, popsycle_config_filename, if skip_perform_pop_syn: optional_cmds += '--skip-perform-pop-syn ' + if skip_make_bhs_single: + optional_cmds += '--skip-make-bhs-single ' + if skip_calc_events: optional_cmds += '--skip-calc-events ' @@ -945,6 +1018,7 @@ def run(output_root='root0', overwrite=False, skip_galaxia=False, skip_perform_pop_syn=False, + skip_make_bhs_single=True, skip_calc_events=False, skip_refine_events=False, skip_refine_binary_events=False): @@ -977,20 +1051,20 @@ def run(output_root='root0', popsycle_config = load_config_file(popsycle_config_filename) if popsycle_config['bin_edges_number'] == 'None': popsycle_config['bin_edges_number'] = None - if popsycle_config['photometric_system'] not in synthetic.photometric_system_dict: - print("""Error: 'photometric_system' in {0} must be a valid option - in 'photometric_system_dict'. - Exiting...""".format(popsycle_config_filename)) - sys.exit(1) - - # Return the dictionary containing PopSyCLE output filenames - filename_dict = _return_filename_dict(output_root) + for photometric_system in popsycle_config['filter_dict']: + if photometric_system not in synthetic.photometric_system_dict: + print("""Error: photometric system: {0} in filter_dict in {1} must be a valid option + in 'photometric_system_dict'. + Exiting...""".format(photometric_system, popsycle_config_filename)) + sys.exit(1) # Prepare additional_photometric_systems - additional_photometric_systems = None - if popsycle_config['photometric_system'] != 'ubv': - additional_photometric_systems = [popsycle_config['photometric_system']] - + additional_photometric_systems = [] + for photometric_system in popsycle_config['filter_dict']: + if photometric_system != 'ubv': + additional_photometric_systems += [photometric_system] + if additional_photometric_systems == []: + additional_photometric_systems = None # Load multiplicity from popsycle_config multiplicity = multiplicity_list[popsycle_config['multiplicity']] # Additional multiplicity classes may require a different method of instantiation @@ -1002,6 +1076,9 @@ def run(output_root='root0', else: hdf5_file_comp = None + # Return the dictionary containing PopSyCLE output filenames + filename_dict = _return_filename_dict(output_root, popsycle_config['filter_dict'], popsycle_config['red_law'], multiplicity) + # Check pipeline stages for valid inputs if not skip_galaxia: _check_run_galaxia(output_root=output_root, @@ -1038,8 +1115,7 @@ def run(output_root='root0', hdf5_file_comp=hdf5_file_comp) if not skip_refine_events: _check_refine_events(input_root=output_root, - filter_name=popsycle_config['filter_name'], - photometric_system=popsycle_config['photometric_system'], + filter_dict=popsycle_config['filter_dict'], red_law=popsycle_config['red_law'], overwrite=overwrite, legacy=False, @@ -1047,18 +1123,16 @@ def run(output_root='root0', hdf5_file_comp=hdf5_file_comp, seed=seed) if not skip_refine_binary_events: - refined_events_filename = '{0:s}_refined_events_' \ - '{1:s}_{2:s}_{3:s}.' \ - 'fits'.format(output_root, - popsycle_config['photometric_system'], - popsycle_config['filter_name'], - popsycle_config['red_law']) - refined_events_comp_filename = refined_events_filename.replace('.fits', '_companions.fits') + # refined_events_filename defaults to using multi_filt, + # unless popsycle_config['filter_dict'] contains only one filter + refined_events_comp_filename = filename_dict['refined_events_filename'].replace('.fits', '_companions.fits') phot_dir = '%s_bin_phot' % output_root - _check_refine_binary_events(events=refined_events_filename, + photmetric_system = list(popsycle_config['filter_dict'])[0] + filter_name = popsycle_config['filter_dict'][photometric_system][0] + _check_refine_binary_events(events=filename_dict['refined_events_filename'], companions=refined_events_comp_filename, - photometric_system=popsycle_config['photometric_system'], - filter_name=popsycle_config['filter_name'], + filter_name=filter_name, + photometric_system=photometric_system, n_proc=n_cores_refine_binary_events, overwrite=overwrite, output_file='default', save_phot=True, @@ -1103,7 +1177,30 @@ def run(output_root='root0', n_proc=n_cores_perform_pop_syn, overwrite=overwrite, seed=seed, - multiplicity=multiplicity) + multiplicity=multiplicity) + + if not skip_make_bhs_single: + print('-- Executing make_bhs_single') + photometric_system_dict = phot_utils.make_photometric_system_dict() + phots = ['ubv_'+ filt for filt in photometric_system_dict['ubv']] + if additional_photometric_systems is not None: + for system in additional_photometric_systems: + phots += [system+'_'+filt for filt in photometric_system_dict[system]] + try: + binary_utils.make_bhs_single( + filename_dict['hdf5_filename'], + filename_dict['hdf5_companions_filename'], + popsycle_config['bbh_frac'], + phots = phots) + except: + binary_utils.make_bhs_single( + filename_dict['hdf5_filename'], + filename_dict['hdf5_companions_filename'], + popsycle_config['bbh_frac'], + symlink_aux_files = False, + phots = phots) + + if not skip_calc_events: # Remove calc_events output if already exists and overwrite=True @@ -1139,16 +1236,13 @@ def run(output_root='root0', t1 = time.time() print('run.py runtime : {0:f} s'.format(t1 - t0)) sys.exit(0) + + # refined_events_filename defaults to using multi_filt, + # unless popsycle_config['filter_dict'] contains only one filter - refined_events_filename = '{0:s}_refined_events_' \ - '{1:s}_{2:s}_{3:s}.' \ - 'fits'.format(output_root, - popsycle_config['photometric_system'], - popsycle_config['filter_name'], - popsycle_config['red_law']) if not skip_refine_events: # Remove refine_events output if already exists and overwrite=True - if _check_for_output(refined_events_filename, overwrite): + if _check_for_output(filename_dict['refined_events_filename'], overwrite): t1 = time.time() print('run.py runtime : {0:f} s'.format(t1 - t0)) sys.exit(1) @@ -1156,8 +1250,7 @@ def run(output_root='root0', # Run refine_events print('-- Executing refine_events') synthetic.refine_events(input_root=output_root, - filter_name=popsycle_config['filter_name'], - photometric_system=popsycle_config['photometric_system'], + filter_dict=popsycle_config['filter_dict'], red_law=popsycle_config['red_law'], overwrite=overwrite, output_file='default', @@ -1165,20 +1258,20 @@ def run(output_root='root0', seed=seed) if multiplicity is not None and not skip_refine_binary_events: - if not os.path.exists(refined_events_filename): + if not os.path.exists(filename_dict['refined_events_filename']): print('Refined events %s missing and therefore ' 'cannot run refine_binary_events. Skipping...' - % refined_events_filename) + % filename_dict['refined_events_filename']) t1 = time.time() print('run.py runtime : {0:f} s'.format(t1 - t0)) sys.exit(1) - refined_events_comp_filename = refined_events_filename.replace('.fits', '_companions.fits') + refined_events_comp_filename = filename_dict['refined_events_filename'].replace('.fits', '_companions.fits') phot_dir = '%s_bin_phot' % output_root - synthetic.refine_binary_events(events=refined_events_filename, + synthetic.refine_binary_events(events=filename_dict['refined_events_filename'], companions=refined_events_comp_filename, - photometric_system=popsycle_config['photometric_system'], - filter_name=popsycle_config['filter_name'], + filter_name=filter_name, + photometric_system=photometric_system, n_proc=n_cores_refine_binary_events, overwrite=overwrite, output_file='default', save_phot=True, @@ -1253,6 +1346,9 @@ def main(): optional.add_argument('--skip-perform-pop-syn', help="Skip running perform_pop_syn.", action='store_true') + optional.add_argument('--skip-make-bhs-single', + help="Skip make_bhs_single.", + action='store_true') optional.add_argument('--skip-calc-events', help="Skip running calc_events.", action='store_true') @@ -1275,6 +1371,7 @@ def main(): overwrite=args.overwrite, skip_galaxia=args.skip_galaxia, skip_perform_pop_syn=args.skip_perform_pop_syn, + skip_make_bhs_single=args.skip_make_bhs_single, skip_calc_events=args.skip_calc_events, skip_refine_events=args.skip_refine_events, skip_refine_binary_events=args.skip_refine_binary_events) diff --git a/popsycle/synthetic.py b/popsycle/synthetic.py index d60dfb8..2f96a41 100755 --- a/popsycle/synthetic.py +++ b/popsycle/synthetic.py @@ -21,7 +21,7 @@ from astropy.table import Table, Column, MaskedColumn from astropy.table import vstack from spisea.imf import imf -from spisea import synthetic, evolution, reddening, ifmr +from spisea import ifmr, synthetic, evolution from spisea.imf.multiplicity import MultiplicityResolvedDK from scipy.spatial import cKDTree import time @@ -37,17 +37,27 @@ import copy from distutils import spawn from popsycle import ebf -from popsycle.filters import transform_ubv_to_ztf +from popsycle.filters import transform_ubv_to_ztf, transform_ubv_to_rubin, transform_ubv_to_roman from popsycle import utils import astropy.units as unit +import astropy.constants as const from popsycle import orbits import pandas as pd from bagle import model +from bagle.orbits import EccAnomalyError from scipy.signal import find_peaks from collections import Counter from operator import itemgetter -from popsycle import binary_utils +from popsycle import binary_utils, phot_utils, lightcurves from astropy.io import fits +from astropy.coordinates import solar_system_ephemeris +from warnings import warn, filterwarnings + +from astropy.io.fits.verify import VerifyWarning +filterwarnings('ignore', category=VerifyWarning, append=True) + +# Use builtin ephemris for popsycle +solar_system_ephemeris.set('builtin') ########## # Conversions. @@ -72,38 +82,15 @@ IFMR_dict['SukhboldN20'] = ifmr.IFMR_N20_Sukhbold() ########## -########## -# Dictionary for extinction law coefficients f_i, as a function of filter -# Damineli values from photometric bands (nm): -# B = 445, V = 551, I = 806, J = 1220, H = 1630, K = 2190, U = 365, R = 658 -# Calculated using calc_f -# Schlegel and Schlafly photometric bands: -# B = 440, V = 543, I = 809, J = 1266, H = 1673, K = 2215, U = 337, R = 651 -# ZTF photometric bands: -# G = 472.274, R = 633.961, I = 788.613 -########## -filt_dict = {} -filt_dict['ubv_J'] = {'Schlafly11': 0.709, 'Schlegel99': 0.902, 'Damineli16': 0.662} -filt_dict['ubv_H'] = {'Schlafly11': 0.449, 'Schlegel99': 0.576, 'Damineli16': 0.344} -filt_dict['ubv_K'] = {'Schlafly11': 0.302, 'Schlegel99': 0.367, 'Damineli16': 0.172} -filt_dict['ubv_U'] = {'Schlafly11': 4.334, 'Schlegel99': 5.434, 'Damineli16': 5.022} -filt_dict['ubv_B'] = {'Schlafly11': 3.626, 'Schlegel99': 4.315, 'Damineli16': 3.757} -filt_dict['ubv_V'] = {'Schlafly11': 2.742, 'Schlegel99': 3.315, 'Damineli16': 2.757} -filt_dict['ubv_I'] = {'Schlafly11': 1.505, 'Schlegel99': 1.940, 'Damineli16': 1.496} -filt_dict['ubv_R'] = {'Schlafly11': 2.169, 'Schlegel99': 2.634, 'Damineli16': 2.102} -filt_dict['ztf_g'] = {'Damineli16': 3.453} -filt_dict['ztf_r'] = {'Damineli16': 2.228} -filt_dict['ztf_i'] = {'Damineli16': 1.553} +filt_dict = phot_utils.make_filt_dict() ########## # Dictionary for listing out supported photometric systems and filters ########## -photometric_system_dict = {} -photometric_system_dict['ubv'] = ['J', 'H', 'K', 'U', 'B', 'V', 'I', 'R'] -photometric_system_dict['ztf'] = ['g', 'r', 'i'] +photometric_system_dict = phot_utils.make_photometric_system_dict() ########## -# List of all supported photometric systems and filters with SPISEA labels +# List of all supported photometric systems and filters with SPISEA labels not including those in additional filters ########## all_filt_list = ['ubv,U', 'ubv,B', 'ubv,V', 'ubv,I', 'ubv,R', 'ukirt,H', 'ukirt,K', 'ukirt,J'] @@ -586,7 +573,7 @@ def perform_pop_syn(ebf_file, output_root, iso_dir, ebf_file : str or ebf file str : name of the ebf file from Galaxia ebf file : actually the ebf file from Galaxia - + output_root : str The thing you want the output files to be named Examples include @@ -611,7 +598,7 @@ def perform_pop_syn(ebf_file, output_root, iso_dir, ``N_bins = (bin_edges_number - 1)**2``. If set to None (default), then number of bins is ``bin_edges_number = int(60 * 2 * radius) + 1`` - + BH_kick_speed_mean : float, optional Mean of the birth kick speed of BH (in km/s) maxwellian distrubution. Defaults to 50 km/s. @@ -635,12 +622,12 @@ def perform_pop_syn(ebf_file, output_root, iso_dir, If set to True, bins files as specified by bin_edges_numbers or default. If set to False, no bins (SET TO FALSE IF DOING FULL SKY DOWNSAMPLED). Default is True. - + overwrite : bool, optional If set to True, overwrites output files. If set to False, exits the function if output files are already on disk. Default is False. - + seed : int, optional If set to non-None, all random sampling will be seeded with the specified seed, forcing identical output for SPISEA and PopSyCLE. @@ -967,8 +954,11 @@ def perform_pop_syn(ebf_file, output_root, iso_dir, popstar_path = os.path.dirname(inspect.getfile(imf)) popsycle_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=popsycle_path).decode('ascii').strip() - popstar_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], + try: + popstar_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=popstar_path).decode('ascii').strip() + except: + popstar_hash = 'Cannot access SPISEA hash' dash_line = '-----------------------------' + '\n' empty_line = '\n' @@ -1233,6 +1223,13 @@ def _process_popsyn_stars_in_bin(bin_idx, age_of_bin, metallicity_of_bin, additional_photometric_systems=additional_photometric_systems, t0=t0, verbose=verbose) + if companions_table is not None: + if star_dict is not None: + if co_dict is not None: + assert(np.sum(star_dict['N_companions']) + np.sum(co_dict['N_companions']) == len(companions_table)) + else: + assert(np.sum(star_dict['N_companions']) == len(companions_table)) + # Save companion table with lock: if companions_table is not None: @@ -1270,6 +1267,7 @@ def _process_popsyn_stars_in_bin(bin_idx, age_of_bin, metallicity_of_bin, co_dict, output_root) _bin_lb_hdf5(lat_bin_edges, long_bin_edges, stars_in_bin, output_root) + else: if co_dict is not None: @@ -1396,7 +1394,7 @@ def _load_galaxia_into_star_dict(star_dict, bin_idx, ebf_file, additional_photom star_dict['ubv_V'] = ebf.read_ind(ebf_file, '/ubv_V', bin_idx) star_dict['ubv_R'] = ebf.read_ind(ebf_file, '/ubv_R', bin_idx) ########## - # Add ztf magnitudes + # Add ztf, rubin, and/or roman magnitudes ########## if additional_photometric_systems is not None: if 'ztf' in additional_photometric_systems: @@ -1414,6 +1412,66 @@ def _load_galaxia_into_star_dict(star_dict, bin_idx, ebf_file, additional_photom star_dict['ztf_i'] = ztf_i del ubv_b, ubv_v, ubv_r, ubv_i, ztf_g, ztf_r, ztf_i + + if 'rubin' in additional_photometric_systems: + # Pull out ubv magnitudes needed for photometric conversions + ubv_u = star_dict['ubv_U'] + ubv_b = star_dict['ubv_B'] + ubv_v = star_dict['ubv_V'] + ubv_r = star_dict['ubv_R'] + ubv_i = star_dict['ubv_I'] + ukirt_j = star_dict['ubv_J'] + + rubin_u = transform_ubv_to_rubin('u', ubv_b, ubv_v, ubv_r, ubv_u, ubv_i, ukirt_j) + rubin_g = transform_ubv_to_rubin('g', ubv_b, ubv_v, ubv_r, ubv_u, ubv_i, ukirt_j) + rubin_r = transform_ubv_to_rubin('r', ubv_b, ubv_v, ubv_r, ubv_u, ubv_i, ukirt_j) + rubin_i = transform_ubv_to_rubin('i', ubv_b, ubv_v, ubv_r, ubv_u, ubv_i, ukirt_j) + rubin_z = transform_ubv_to_rubin('z', ubv_b, ubv_v, ubv_r, ubv_u, ubv_i, ukirt_j) + rubin_y = transform_ubv_to_rubin('y', ubv_b, ubv_v, ubv_r, ubv_u, ubv_i, ukirt_j) + star_dict['rubin_u'] = rubin_u + star_dict['rubin_g'] = rubin_g + star_dict['rubin_r'] = rubin_r + star_dict['rubin_i'] = rubin_i + star_dict['rubin_z'] = rubin_z + star_dict['rubin_y'] = rubin_y + + del ubv_u,ubv_b, ubv_v, ubv_r, ubv_i, ukirt_j, rubin_u, rubin_g, rubin_r, rubin_i, rubin_z, rubin_y + + if 'roman' in additional_photometric_systems: + # Pull out ubv magnitudes needed for photometric conversions + ubv_v = star_dict['ubv_V'] + ubv_r = star_dict['ubv_R'] + ubv_i = star_dict['ubv_I'] + ukirt_j = star_dict['ubv_J'] + ukirt_h = star_dict['ubv_H'] + ukirt_k = star_dict['ubv_K'] + + roman_f062 = transform_ubv_to_roman('f062',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + roman_f087 = transform_ubv_to_roman('f087',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + roman_f106 = transform_ubv_to_roman('f106',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + roman_f129 = transform_ubv_to_roman('f129',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + roman_f158 = transform_ubv_to_roman('f158',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + roman_f146 = transform_ubv_to_roman('f146',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + roman_f184 = transform_ubv_to_roman('f184',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + roman_f213 = transform_ubv_to_roman('f213',ubv_V = ubv_v, ubv_R = ubv_r, ubv_I = ubv_i, ukirt_J = ukirt_j, ukirt_H = ukirt_h, ukirt_K = ukirt_k) + + star_dict['roman_f062'] = roman_f062 + star_dict['roman_f087'] = roman_f087 + star_dict['roman_f106'] = roman_f106 + star_dict['roman_f129'] = roman_f129 + star_dict['roman_f158'] = roman_f158 + star_dict['roman_f146'] = roman_f146 + star_dict['roman_f184'] = roman_f184 + star_dict['roman_f213'] = roman_f213 + + del ubv_v, ubv_r, ubv_i, ukirt_j, ukirt_h, ukirt_k, roman_f062, roman_f087, roman_f106, roman_f129, roman_f158, roman_f146, roman_f184, roman_f213 + + if 'sdss' in additional_photometric_systems: + star_dict['sdss_u'] = ebf.read_ind(ebf_file, '/sdss_u', bin_idx) + star_dict['sdss_g'] = ebf.read_ind(ebf_file, '/sdss_g', bin_idx) + star_dict['sdss_r'] = ebf.read_ind(ebf_file, '/sdss_r', bin_idx) + star_dict['sdss_i'] = ebf.read_ind(ebf_file, '/sdss_i', bin_idx) + star_dict['sdss_z'] = ebf.read_ind(ebf_file, '/sdss_z', bin_idx) def _get_bin_edges(l, b, surveyArea, bin_edges_number): @@ -1568,6 +1626,12 @@ def _make_co_dict(log_age, if additional_photometric_systems is not None: if 'ztf' in additional_photometric_systems: keep_columns += ['m_ztf_g', 'm_ztf_r', 'm_ztf_i'] + if 'sdss' in additional_photometric_systems: + keep_columns += ['m_sdss_u', 'm_sdss_g', 'm_sdss_r', 'm_sdss_i', 'm_sdss_z'] + if 'rubin' in additional_photometric_systems: + keep_columns += ['m_rubin_u', 'm_rubin_g', 'm_rubin_r', 'm_rubin_i', 'm_rubin_z', 'm_rubin_y'] + if 'roman' in additional_photometric_systems: + keep_columns += ['m_roman_f062', 'm_roman_f087', 'm_roman_f106', 'm_roman_f129', 'm_roman_f158', 'm_roman_f146', 'm_roman_f184', 'm_roman_f213'] co_table.keep_columns(keep_columns) # Fill out the rest of co_dict @@ -1684,6 +1748,29 @@ def _make_co_dict(log_age, co_dict['ztf_g'] = np.full(len(co_dict['vx']), np.nan) co_dict['ztf_r'] = np.full(len(co_dict['vx']), np.nan) co_dict['ztf_i'] = np.full(len(co_dict['vx']), np.nan) + if 'sdss' in additional_photometric_systems: + co_dict['sdss_u'] = np.full(len(co_dict['vx']), np.nan) + co_dict['sdss_g'] = np.full(len(co_dict['vx']), np.nan) + co_dict['sdss_r'] = np.full(len(co_dict['vx']), np.nan) + co_dict['sdss_i'] = np.full(len(co_dict['vx']), np.nan) + co_dict['sdss_z'] = np.full(len(co_dict['vx']), np.nan) + if 'rubin' in additional_photometric_systems: + co_dict['rubin_u'] = np.full(len(co_dict['vx']), np.nan) + co_dict['rubin_g'] = np.full(len(co_dict['vx']), np.nan) + co_dict['rubin_r'] = np.full(len(co_dict['vx']), np.nan) + co_dict['rubin_i'] = np.full(len(co_dict['vx']), np.nan) + co_dict['rubin_z'] = np.full(len(co_dict['vx']), np.nan) + co_dict['rubin_y'] = np.full(len(co_dict['vx']), np.nan) + if 'roman' in additional_photometric_systems: + co_dict['roman_f062'] = np.full(len(co_dict['vx']), np.nan) + co_dict['roman_f087'] = np.full(len(co_dict['vx']), np.nan) + co_dict['roman_f106'] = np.full(len(co_dict['vx']), np.nan) + co_dict['roman_f129'] = np.full(len(co_dict['vx']), np.nan) + co_dict['roman_f158'] = np.full(len(co_dict['vx']), np.nan) + co_dict['roman_f146'] = np.full(len(co_dict['vx']), np.nan) + co_dict['roman_f184'] = np.full(len(co_dict['vx']), np.nan) + co_dict['roman_f213'] = np.full(len(co_dict['vx']), np.nan) + ######### # Initialize values for compact object teff, specific gravity and bolometric luminosity @@ -1730,6 +1817,28 @@ def _make_co_dict(log_age, co_dict['ztf_g'][lum_co_sys_idx] = co_table['m_ztf_g'][lum_co_sys_idx].data co_dict['ztf_r'][lum_co_sys_idx] = co_table['m_ztf_r'][lum_co_sys_idx].data co_dict['ztf_i'][lum_co_sys_idx] = co_table['m_ztf_i'][lum_co_sys_idx].data + if 'sdss' in additional_photometric_systems: + co_dict['sdss_u'][lum_co_sys_idx] = co_table['m_sdss_u'][lum_co_sys_idx].data + co_dict['sdss_g'][lum_co_sys_idx] = co_table['m_sdss_g'][lum_co_sys_idx].data + co_dict['sdss_r'][lum_co_sys_idx] = co_table['m_sdss_r'][lum_co_sys_idx].data + co_dict['sdss_i'][lum_co_sys_idx] = co_table['m_sdss_i'][lum_co_sys_idx].data + co_dict['sdss_z'][lum_co_sys_idx] = co_table['m_sdss_z'][lum_co_sys_idx].data + if 'rubin' in additional_photometric_systems: + co_dict['rubin_u'][lum_co_sys_idx] = co_table['m_rubin_u'][lum_co_sys_idx].data + co_dict['rubin_g'][lum_co_sys_idx] = co_table['m_rubin_g'][lum_co_sys_idx].data + co_dict['rubin_r'][lum_co_sys_idx] = co_table['m_rubin_r'][lum_co_sys_idx].data + co_dict['rubin_i'][lum_co_sys_idx] = co_table['m_rubin_i'][lum_co_sys_idx].data + co_dict['rubin_z'][lum_co_sys_idx] = co_table['m_rubin_z'][lum_co_sys_idx].data + co_dict['rubin_y'][lum_co_sys_idx] = co_table['m_rubin_y'][lum_co_sys_idx].data + if 'roman' in additional_photometric_systems: + co_dict['roman_f062'][lum_co_sys_idx] = co_table['m_roman_f062'][lum_co_sys_idx].data + co_dict['roman_f087'][lum_co_sys_idx] = co_table['m_roman_f087'][lum_co_sys_idx].data + co_dict['roman_f106'][lum_co_sys_idx] = co_table['m_roman_f106'][lum_co_sys_idx].data + co_dict['roman_f129'][lum_co_sys_idx] = co_table['m_roman_f129'][lum_co_sys_idx].data + co_dict['roman_f158'][lum_co_sys_idx] = co_table['m_roman_f158'][lum_co_sys_idx].data + co_dict['roman_f146'][lum_co_sys_idx] = co_table['m_roman_f146'][lum_co_sys_idx].data + co_dict['roman_f184'][lum_co_sys_idx] = co_table['m_roman_f184'][lum_co_sys_idx].data + co_dict['roman_f213'][lum_co_sys_idx] = co_table['m_roman_f213'][lum_co_sys_idx].data # Memory cleaning del co_table @@ -2024,6 +2133,12 @@ def _make_cluster(iso_dir, log_age, currentClusterMass, if additional_photometric_systems is not None: if 'ztf' in additional_photometric_systems: my_filt_list += ['ztf,g', 'ztf,r', 'ztf,i'] + if 'sdss' in additional_photometric_systems: + my_filt_list += ['sdss,u', 'sdss,g', 'sdss,r', 'sdss,i', 'sdss,z'] + if 'rubin' in additional_photometric_systems: + my_filt_list += ['rubin,u', 'rubin,g', 'rubin,r', 'rubin,i', 'rubin,z', 'rubin,y'] + if 'roman' in additional_photometric_systems: + my_filt_list += ['roman,wfi,f062', 'roman,wfi,f087', 'roman,wfi,f106', 'roman,wfi,f129', 'roman,wfi,f158', 'roman,wfi,f146', 'roman,wfi,f184', 'roman,wfi,f213'] # Calculate the initial cluster mass # changed from 0.08 to 0.11 at start because MIST can't handle. @@ -2047,25 +2162,13 @@ def _make_cluster(iso_dir, log_age, currentClusterMass, # -- arbitrarily chose AKs = 0, distance = 10 pc # (irrelevant, photometry not used) # Using MIST models to get white dwarfs - my_iso = synthetic.IsochronePhot(log_age, 0, 10, + with lock: + my_iso = synthetic.IsochronePhot(log_age, 0, 10, evo_model=evolution.MISTv1(), filters=my_filt_list, iso_dir=iso_dir, metallicity=feh) - # Check that the isochrone has all of the filters in filt_list - # If not, force recreating the isochrone with recomp=True - my_iso_filters = [f for f in my_iso.points.colnames if 'm_' in f] - my_filt_list_fmt = ['m_%s' % f.replace(',', '_') for f in my_filt_list] - # Checks if the list of filters are different - if len(set(my_filt_list_fmt) - set(my_iso_filters)) > 0: - my_iso = synthetic.IsochronePhot(log_age, 0, 10, - evo_model=evolution.MISTv1(), - filters=my_filt_list, - iso_dir=iso_dir, - recomp=True, - metallicity=feh) - # !!! Keep trunc_kroupa out here !!! Death and destruction otherwise. # DON'T MOVE IT OUT! trunc_kroupa = imf.IMF_broken_powerlaw(massLimits, powers, multiplicity=multiplicity) @@ -2141,6 +2244,9 @@ def _make_cluster(iso_dir, log_age, currentClusterMass, cluster.companions.remove_rows(bad_companions) + if cluster is not None and multiplicity is not None: + assert(len(cluster.companions) == np.sum(cluster.star_systems['N_companions'])) + return cluster, unmade_cluster_counter, unmade_cluster_mass @@ -2874,7 +2980,8 @@ def _make_companions_table(cluster, star_dict, co_dict, co_dict['systemMass'][CO_idx_w_companions[1]] += CO_companions_system_mass if verbose > 3: print(f'test2 {time.time() - t0:.2f} sec') - + + assert(len(compact_companions) == np.sum(co_dict['N_companions'])) del co_dict_tmp ########### @@ -2913,6 +3020,28 @@ def _make_companions_table(cluster, star_dict, co_dict, companions_system_m_ztf_g = grouped_companions['m_ztf_g'].groups.aggregate(binary_utils.add_magnitudes) companions_system_m_ztf_r = grouped_companions['m_ztf_r'].groups.aggregate(binary_utils.add_magnitudes) companions_system_m_ztf_i = grouped_companions['m_ztf_i'].groups.aggregate(binary_utils.add_magnitudes) + if 'sdss' in additional_photometric_systems: + companions_system_m_sdss_u = grouped_companions['m_sdss_u'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_sdss_g = grouped_companions['m_sdss_g'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_sdss_r = grouped_companions['m_sdss_r'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_sdss_i = grouped_companions['m_sdss_i'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_sdss_z = grouped_companions['m_sdss_z'].groups.aggregate(binary_utils.add_magnitudes) + if 'rubin' in additional_photometric_systems: + companions_system_m_rubin_u = grouped_companions['m_rubin_u'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_rubin_g = grouped_companions['m_rubin_g'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_rubin_r = grouped_companions['m_rubin_r'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_rubin_i = grouped_companions['m_rubin_i'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_rubin_z = grouped_companions['m_rubin_z'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_rubin_y = grouped_companions['m_rubin_y'].groups.aggregate(binary_utils.add_magnitudes) + if 'roman' in additional_photometric_systems: + companions_system_m_roman_f062 = grouped_companions['m_roman_f062'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_roman_f087 = grouped_companions['m_roman_f087'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_roman_f106 = grouped_companions['m_roman_f106'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_roman_f129 = grouped_companions['m_roman_f129'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_roman_f158 = grouped_companions['m_roman_f158'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_roman_f146 = grouped_companions['m_roman_f146'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_roman_f184 = grouped_companions['m_roman_f184'].groups.aggregate(binary_utils.add_magnitudes) + companions_system_m_roman_f213 = grouped_companions['m_roman_f213'].groups.aggregate(binary_utils.add_magnitudes) star_dict['ubv_I'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['ubv_I'][group_companions_system_idxs], companions_system_m_ubv_I]) @@ -2928,7 +3057,46 @@ def _make_companions_table(cluster, star_dict, co_dict, star_dict['ztf_g'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['ztf_g'][group_companions_system_idxs], companions_system_m_ztf_g]) star_dict['ztf_r'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['ztf_r'][group_companions_system_idxs], companions_system_m_ztf_r]) star_dict['ztf_i'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['ztf_i'][group_companions_system_idxs], companions_system_m_ztf_i]) + if 'sdss' in additional_photometric_systems: + star_dict['sdss_u'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['sdss_u'][group_companions_system_idxs], companions_system_m_sdss_u]) + star_dict['sdss_g'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['sdss_g'][group_companions_system_idxs], companions_system_m_sdss_g]) + star_dict['sdss_r'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['sdss_r'][group_companions_system_idxs], companions_system_m_sdss_r]) + star_dict['sdss_i'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['sdss_i'][group_companions_system_idxs], companions_system_m_sdss_i]) + star_dict['sdss_z'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['sdss_z'][group_companions_system_idxs], companions_system_m_sdss_z]) + if 'rubin' in additional_photometric_systems: + star_dict['rubin_u'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['rubin_u'][group_companions_system_idxs], companions_system_m_rubin_u]) + star_dict['rubin_g'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['rubin_g'][group_companions_system_idxs], companions_system_m_rubin_g]) + star_dict['rubin_r'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['rubin_r'][group_companions_system_idxs], companions_system_m_rubin_r]) + star_dict['rubin_i'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['rubin_i'][group_companions_system_idxs], companions_system_m_rubin_i]) + star_dict['rubin_z'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['rubin_z'][group_companions_system_idxs], companions_system_m_rubin_z]) + star_dict['rubin_y'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['rubin_y'][group_companions_system_idxs], companions_system_m_rubin_y]) + if 'roman' in additional_photometric_systems: + star_dict['roman_f062'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f062'][group_companions_system_idxs], companions_system_m_roman_f062]) + star_dict['roman_f087'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f087'][group_companions_system_idxs], companions_system_m_roman_f087]) + star_dict['roman_f106'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f106'][group_companions_system_idxs], companions_system_m_roman_f106]) + star_dict['roman_f129'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f129'][group_companions_system_idxs], companions_system_m_roman_f129]) + star_dict['roman_f158'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f158'][group_companions_system_idxs], companions_system_m_roman_f158]) + star_dict['roman_f146'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f146'][group_companions_system_idxs], companions_system_m_roman_f146]) + star_dict['roman_f184'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f184'][group_companions_system_idxs], companions_system_m_roman_f184]) + star_dict['roman_f213'][group_companions_system_idxs] = binary_utils.add_magnitudes([star_dict['roman_f213'][group_companions_system_idxs], companions_system_m_roman_f213]) + + # Removes unused columns to conserve memory. + keep_columns = ['system_idx', 'zams_mass', 'Teff', 'L', 'logg', 'isWR', 'mass', 'phase', 'metallicity', + 'm_ubv_I', 'm_ubv_R', 'm_ubv_B', 'm_ubv_U', 'm_ubv_V', 'm_ukirt_H', 'm_ukirt_J', 'm_ukirt_K', + 'log_a', 'e', 'i', 'Omega', 'omega'] + + if additional_photometric_systems is not None: + if 'ztf' in additional_photometric_systems: + keep_columns += ['m_ztf_g', 'm_ztf_r', 'm_ztf_i'] + if 'sdss' in additional_photometric_systems: + keep_columns += ['m_sdss_u', 'm_sdss_g', 'm_sdss_r', 'm_sdss_i', 'm_sdss_z'] + if 'rubin' in additional_photometric_systems: + keep_columns += ['m_rubin_u', 'm_rubin_g', 'm_rubin_r', 'm_rubin_i', 'm_rubin_z', 'm_rubin_y'] + if 'roman' in additional_photometric_systems: + keep_columns += ['m_roman_f062', 'm_roman_f087', 'm_roman_f106', 'm_roman_f129', 'm_roman_f158', 'm_roman_f146', 'm_roman_f184', 'm_roman_f213'] + companions_table.keep_columns(keep_columns) + assert(len(companions_table) == np.sum(star_dict['N_companions'])) # Switch companion table to point to obj_id instead of idx companions_table['system_idx'] = star_dict['obj_id'][companions_table['system_idx']] @@ -3971,9 +4139,8 @@ def _convert_photometric_99_to_nan(table, photometric_system='ubv'): table[name][cond] = np.nan -def _check_refine_events(input_root, filter_name, - photometric_system, red_law, overwrite, - output_file, hdf5_file_comp, legacy, seed): +def _check_refine_events(input_root, filter_dict, red_law, overwrite, output_file, + hdf5_file_comp, legacy, seed, filter_name=None, photometric_system=None): """ Checks that the inputs of refine_events are valid @@ -3982,14 +4149,14 @@ def _check_refine_events(input_root, filter_name, input_root : str The root path and name of the *_events.fits and *_blends.fits. Don't include those suffixes yet. - - filter_name : str - The name of the filter in which to calculate all the - microlensing events. The filter name convention is set - in the global filt_dict parameter at the top of this module. - - photometric_system : str - The name of the photometric system in which the filter exists. + + filter_dict : dict + Dictionary with desired photometric systems and filters to calculate microlensing events for. + The dictionary keys are photometric systems. + The dictionary values are lists of strings filled with filters within that photometric system key. + Example: + To calculate the events for UBV U, and ZTF, u, g, r: + filter_dict = {'ubv':['U'],'ztf':['u','g','r']} red_law : str The name of the reddening law to use from SPISEA. @@ -4008,16 +4175,34 @@ def _check_refine_events(input_root, filter_name, seed : None or int If not None, this forces the random orbit time for binaries to be fixed every time. If seed is added but there are no binaries, the seed will have no consequence. + + filter_name : str, optional, DEPRECATED + The name of the filter in which to calculate all the + microlensing events. The filter name convention is set + in the global filt_dict parameter at the top of this module. + + photometric_system : str, optional, DEPRECATED + The name of the photometric system in which the filter exists. """ if not isinstance(input_root, str): raise Exception('input_root (%s) must be a string.' % str(input_root)) + + if filter_dict is not None: + if not isinstance(filter_dict, dict): + raise Exception('filter_dict (%s) must be a dictionary.' % str(filter_dict)) + if not all(isinstance(key,str) for key in filter_dict): + raise Exception('All filter_dict keys must be strings.') + if not all(isinstance(filt,str) for key,val in filter_dict.items() for filt in val): + raise Exception('All filter_dict vaues must be lists of strings.') + + if filter_name is not None: + if not isinstance(filter_name, str): + raise Exception('filter_name (%s) must be a string.' % str(filter_name)) - if not isinstance(filter_name, str): - raise Exception('filter_name (%s) must be a string.' % str(filter_name)) - - if not isinstance(photometric_system, str): - raise Exception('photometric_system (%s) must be a string.' % str(photometric_system)) + if photometric_system is not None: + if not isinstance(photometric_system, str): + raise Exception('photometric_system (%s) must be a string.' % str(photometric_system)) if not isinstance(red_law, str): raise Exception('red_law (%s) must be a string.' % str(red_law)) @@ -4038,40 +4223,56 @@ def _check_refine_events(input_root, filter_name, if seed is not None: if not isinstance(seed, int): raise Exception('seed (%s) must be None or an integer.' % str(seed)) - - # Check to see that the filter name, photometric system, red_law are valid - if photometric_system not in photometric_system_dict: - exception_str = 'photometric_system must be a key in ' \ - 'photometric_system_dict. \n' \ - 'Acceptable values are : ' - for photometric_system in photometric_system_dict: - exception_str += '%s, ' % photometric_system - exception_str = exception_str[:-2] - raise Exception(exception_str) - - if filter_name not in photometric_system_dict[photometric_system]: - exception_str = 'filter_name must be a value in ' \ - 'photometric_system_dict[%s]. \n' \ - 'Acceptable values are : ' % photometric_system - for filter_name in photometric_system_dict[photometric_system]: - exception_str += '%s, ' % filter_name - exception_str = exception_str[:-2] - raise Exception(exception_str) - - key = photometric_system + '_' + filter_name - if red_law not in filt_dict[key]: - exception_str = 'red_law must be a value in ' \ - 'filt_dict[%s]. \n' \ - 'Acceptable values are : ' % key - for red_law in filt_dict[key]: - exception_str += '%s, ' % red_law - exception_str = exception_str[:-2] - raise Exception(exception_str) + + # Check that either filter_dict is provided, or both filter_name and photometric_system are provided + if filter_dict is None and ((filter_name is None) or (photometric_system is None)): + raise Exception('Either filter_dict or filter_name + photometric_system must be provided.') + + if (filter_dict is not None) and ((filter_name is not None) or (photometric_system is not None)): + raise Exception('Both filter_dict and either filter_name or photometric_system was provided. \n'\ + 'Please provide either filter_dict or filter_name + photometric_system') + + if (filter_name is None) ^ (photometric_system is None): + raise Exception('Either filter_name or photometric_system was provided without the other. Please provide both') + + # Check to see that the filter dictionary, and red_law are valid + if (filter_name is not None) and (photometric_system is not None): + filter_dict = {} + filter_dict[photometric_system] = [filter_name] + for system in filter_dict: + if system not in photometric_system_dict: + exception_str = 'photometric_system must be a key in ' \ + 'photometric_system_dict. \n' \ + 'Acceptable values are : ' + for photometric_system in photometric_system_dict: + exception_str += '%s, ' % photometric_system + exception_str = exception_str[:-2] + raise Exception(exception_str) + + for filt in filter_dict[system]: + if filt not in photometric_system_dict[system]: + exception_str = 'filter_name must be a value in ' \ + 'photometric_system_dict[%s]. \n' \ + 'Acceptable values are : ' % system + for filter_name in photometric_system_dict[system]: + exception_str += '%s, ' % filter_name + exception_str = exception_str[:-2] + raise Exception(exception_str) + + key = system + '_' + filt + if red_law not in filt_dict[key]: + exception_str = 'red_law must be a value in ' \ + 'filt_dict[%s]. \n' \ + 'Acceptable values are : ' % key + for red_law in filt_dict[key]: + exception_str += '%s, ' % red_law + exception_str = exception_str[:-2] + raise Exception(exception_str) -def refine_events(input_root, filter_name, photometric_system, red_law, - overwrite=False, - output_file='default', hdf5_file_comp=None, legacy = False, seed = None): +def refine_events(input_root, red_law, filter_dict = None, filter_name = None, photometric_system = None, + overwrite=False, output_file='default', hdf5_file_comp=None, legacy = False, seed = None, + galactic_model_code = 'galaxia'): """ Takes the output Astropy table from calc_events, and from that calculates the time of closest approach. Will also return source-lens @@ -4083,14 +4284,14 @@ def refine_events(input_root, filter_name, photometric_system, red_law, The root path and name of the \*_events.fits, \*_blends.fits, \*_galaxia_params.txt, \*_calc_events.log, and \_*_perform_pop_syn.log. Don't include those suffixes yet. - - filter_name : str - The name of the filter in which to calculate all the - microlensing events. The filter name convention is set - in the global filt_dict parameter at the top of this module. - - photometric_system : str - The name of the photometric system in which the filter exists. + + filter_dict : dict + Dictionary with desired photometric systems and filters to calculate microlensing events for. + The dictionary keys are photometric systems. + The dictionary values are lists of strings filled with filters within that photometric system key. + Example: + To calculate the events for UBV U, and ZTF, u, g, r: + filter_dict = {'ubv':['U'],'ztf':['u','g','r']} red_law : str The name of the reddening law to use from SPISEA. @@ -4118,6 +4319,14 @@ def refine_events(input_root, filter_name, photometric_system, red_law, If not None, this forces the random orbit time for binaries to be fixed every time. If seed is added but there are no binaries, the seed will have no consequence. Default is None. + + filter_name : str, optional, DEPRECATED + The name of the filter in which to calculate all the + microlensing events. The filter name convention is set + in the global filt_dict parameter at the top of this module. + + photometric_system : str, optional, DEPRECATED + The name of the photometric system in which the filter exists. Returns ------- @@ -4140,32 +4349,50 @@ def refine_events(input_root, filter_name, photometric_system, red_law, 'Either delete the .fits file, or pick a new name.') # Error handling/complaining if input types are not right. - _check_refine_events(input_root, filter_name, - photometric_system, red_law, - overwrite, output_file, hdf5_file_comp, legacy, seed) - + _check_refine_events(input_root, filter_dict, red_law, overwrite, output_file, hdf5_file_comp, + legacy, seed, filter_name=filter_name, photometric_system=photometric_system) + + if (filter_name is not None) & (photometric_system is not None): + warn('filter_name and photometric_system are deprecated, please use filter_dict', DeprecationWarning, stacklevel=2) + filter_dict = {photometric_system:[filter_name]} + if output_file == 'default': - output_file = '{0:s}_refined_events_{1:s}_{2:s}_{3:s}.fits'.format(input_root, - photometric_system, - filter_name, - red_law) + system = list(filter_dict.keys())[0] + filt = filter_dict[system][0] + if len(filter_dict) == 1 and len(filter_dict[system]) == 1: + output_file = '{0:s}_refined_events_{1:s}_{2:s}_{3:s}.fits'.format(input_root, + system, + filt, + red_law) + else: + output_file = '{0:s}_refined_events_multi_filt_{1:s}.fits'.format(input_root, + red_law) t_0 = time.time() event_fits_file = input_root + '_events.fits' blend_fits_file = input_root + '_blends.fits' - galaxia_params_file = input_root + '_galaxia_params.txt' + galaxia_params_file = input_root + '_'+galactic_model_code+'_params.txt' calc_events_log_file = input_root + '_calc_events.log' perform_pop_syn_log_file = input_root + '_perform_pop_syn.log' - for filename in [event_fits_file, - blend_fits_file, - galaxia_params_file, - calc_events_log_file, - perform_pop_syn_log_file]: - if not os.path.exists(filename): - raise Exception(f'{filename} cannot be found.') + if galactic_model_code=='galaxia': + for filename in [event_fits_file, + blend_fits_file, + galaxia_params_file, + calc_events_log_file, + perform_pop_syn_log_file]: + if not os.path.exists(filename): + raise Exception(f'{filename} cannot be found.') + else: + for filename in [event_fits_file, + blend_fits_file, + galaxia_params_file, + calc_events_log_file]: + if not os.path.exists(filename): + raise Exception(f'{filename} cannot be found.') + event_tab = Table.read(event_fits_file) blend_tab = Table.read(blend_fits_file) @@ -4180,11 +4407,16 @@ def refine_events(input_root, filter_name, photometric_system, red_law, # If photometric fields contain -99, convert to nan - _convert_photometric_99_to_nan(event_tab, photometric_system) - _convert_photometric_99_to_nan(blend_tab, photometric_system) + photometric_system = filter_dict.keys() + filter_name = filter_dict.values() + for system in photometric_system: + _convert_photometric_99_to_nan(event_tab, system) + _convert_photometric_99_to_nan(blend_tab, system) # Only keep events with luminous sources - event_tab = event_tab[~np.isnan(event_tab[photometric_system + '_' + filter_name + '_S'])] + for system in filter_dict: + for filters in filter_dict[system]: + event_tab = event_tab[~np.isnan(event_tab[system + '_' + filters + '_S'])] # Grab the obs_time from the calc_events log with open(calc_events_log_file, 'r') as my_file: @@ -4203,16 +4435,19 @@ def refine_events(input_root, filter_name, photometric_system, red_law, break # Grab the random seed from the perform_pop_syn log - with open(perform_pop_syn_log_file, 'r') as my_file: - for num, line in enumerate(my_file): - - if 'seed ' == line.split(',')[0]: - pps_seed = line.split(',')[1].replace('\n', '') - try: - pps_seed = int(pps_seed) - except: - pps_seed = np.nan - break + try: + with open(perform_pop_syn_log_file, 'r') as my_file: + for num, line in enumerate(my_file): + + if 'seed ' == line.split(',')[0]: + pps_seed = line.split(',')[1].replace('\n', '') + try: + pps_seed = int(pps_seed) + except: + pps_seed = np.nan + break + except: + pps_seed=np.nan # Sets random seed to nan for legacy files unless # some value was set manually @@ -4253,7 +4488,9 @@ def refine_events(input_root, filter_name, photometric_system, red_law, event_tab['t_E'] = t_E # days # Add stuff to event_tab... shouldn't have any direct outputs - _calc_observables(filter_name, red_law, event_tab, blend_tab, photometric_system) + for system in filter_dict: + for filters in filter_dict[system]: + _calc_observables(filters, red_law, event_tab, blend_tab, system) # Relative parallax pi_rel = event_tab['rad_L'] ** -1 - event_tab['rad_S'] ** -1 @@ -4268,6 +4505,12 @@ def refine_events(input_root, filter_name, photometric_system, red_law, event_tab['gal_seed'] = np.ones(len(event_tab)) * gal_seed event_tab.write(output_file, overwrite=overwrite) + + # Add the filter_dict as a header to store metadata # CHANGE HERE + filters_string = ', '.join([': '.join([system, ', '.join(filts)]) for system , filts in filter_dict.items()]) + with fits.open(output_file, mode='update') as hdul: + header = hdul[0].header + header['filters'] = filters_string @@ -4391,7 +4634,7 @@ def refine_events(input_root, filter_name, photometric_system, red_law, line0 = 'FUNCTION INPUT PARAMETERS' + '\n' line1 = 'input_root : ' + input_root + '\n' - line2 = 'filter_name : ' + filter_name + '\n' + line2 = 'filter_dict : ' + ', '.join([': '.join([system, ', '.join(filts)]) for system , filts in filter_dict.items()]) + '\n' line3 = 'red_law : ' + red_law + '\n' line4 = 'VERSION INFORMATION' + '\n' @@ -4405,14 +4648,20 @@ def refine_events(input_root, filter_name, photometric_system, red_law, line11 = str(N_events_survey) + ' : candidate events in survey window' + '\n' line12 = 'FILES CREATED' + '\n' - line13 = output_file + ' : refined events' + line13 = output_file + ' : refined events' + '\n' line14 = '\n' #By default no companion file created if hdf5_file_comp is not None: if len(companion_table) > 0: line14 = output_file[:-5] + "_companions.fits" + ' : companions refined events' - - with open(input_root + '_refined_events_' + photometric_system + '_' + filter_name + '_' + red_law + '.log', 'w') as out: + + system = list(filter_dict.keys())[0] + filt = filter_dict[system][0] + if len(filter_dict) == 1 and len(filter_dict[system]) == 1: + filter_string = system + '_' + filt + else: + filter_string = 'multi_filt' + with open(input_root + '_refined_events_' + filter_string + '_' + red_law + '.log', 'w') as out: out.writelines([line0, dash_line, line1, line2, line3, empty_line, line4, dash_line, line5, line6, line7, empty_line, line8, dash_line, line9, line10, line11, empty_line, @@ -4726,16 +4975,17 @@ def _add_binary_angles(companion_table, event_table): del event_table_df start_time_bin_angles = time.time() - alphas, phi_pi_Es, phis = calculate_binary_angles(companion_tmp_df_joined) + alphas, phi_pi_Es, phis, tps = calculate_binary_angles(companion_tmp_df_joined) companion_tmp_df_joined['alpha'] = alphas companion_tmp_df_joined['phi_pi_E'] = phi_pi_Es companion_tmp_df_joined['phi'] = phis + companion_tmp_df_joined['tp'] = tps # reset index adds obj_id_L and obj_id_S back as their own columns # doing list(cluster.companions.columns) + those other names means that it only takes columns # that were in cluster.companions and obj_id_L, obj_id_S, alpha,... - companion_tmp_df = companion_tmp_df_joined.reset_index()[list(companion_tmp_df.columns) + ['obj_id_L', 'obj_id_S'] + ['alpha', 'phi_pi_E', 'phi']] + companion_tmp_df = companion_tmp_df_joined.reset_index()[list(companion_tmp_df.columns) + ['obj_id_L', 'obj_id_S'] + ['alpha', 'phi_pi_E', 'phi', 'tp']] #print('full bin angles loop', time.time() - start_time_bin_angles) del companion_tmp_df_joined @@ -4773,6 +5023,7 @@ def calculate_binary_angles(joined_table): alphas = [] phi_pi_Es = [] phis = [] + tps = [] for index, row in joined_table.iterrows(): @@ -4797,6 +5048,8 @@ def calculate_binary_angles(joined_table): orb.p = row['P'] # [years] orb.t0 = np.random.rand()*row['P'] + row['t0'] # [years] This is initial orb.mass = row['systemMass_{}'.format(prim_type)] # [Msun] + + tps.append(orb.t0) # Position of the companion when primary at origin @@ -4901,7 +5154,7 @@ def calculate_binary_angles(joined_table): phi_pi_Es.append(phi_pi_E) phis.append(phi) - return alphas, phi_pi_Es, phis + return alphas, phi_pi_Es, phis, tps def _add_multiples_parameters(companion_table, event_table): """ @@ -4990,7 +5243,7 @@ def _check_refine_binary_events(events, companions, photometric_system : str The name of the photometric system in which the filter exists. - filter_name : str + filter_name : str The name of the filter in which to calculate all the microlensing events. The filter name convention is set in the global filt_dict parameter at the top of this module. @@ -5027,11 +5280,11 @@ def _check_refine_binary_events(events, companions, if not isinstance(companions, str): raise Exception('companions (%s) must be a string.' % str(companions)) - if not isinstance(photometric_system, str): - raise Exception('photometric_system (%s) must be a string.' % str(photometric_system)) - if not isinstance(filter_name, str): - raise Exception('filter_name (%s) must be a string.' % str(filter_name)) + raise Exception('filter_name (%s) must be a string.' % str(filter_name)) + + if not isinstance(photometric_system, str): + raise Exception('photometric_system (%s) must be a string.' % str(photometric_system)) if not isinstance(output_file, str): raise Exception('output_file (%s) must be a string.' % str(output_file)) @@ -5092,7 +5345,7 @@ def refine_binary_events(events, companions, photometric_system, filter_name, companions : str fits file containing the companions calculated from refine_events - photometric_system : str + photometric_system : str The name of the photometric system in which the filter exists. filter_name : str @@ -5171,7 +5424,7 @@ def refine_binary_events(events, companions, photometric_system, filter_name, event_table['f_blend_%s' % filter_name] = event_table['f_blend_%s' % filter_name] # None of these should be nan if type(comp_table['m_%s_%s' % (photometric_system, filter_name)]) == np.ma.core.MaskedArray or type(comp_table['m_%s_%s' % (photometric_system, filter_name)]) == MaskedColumn: - comp_table['m_%s_%s' % (photometric_system, filter_name)] = comp_table['m_%s_%s' % (photometric_system, filter_name)].filled(np.nan) + comp_table['m_%s_%s' % (photometric_system, filter_name)] = comp_table['m_%s_%s' % (photometric_system, filter_name)].filled(np.nan) event_table.add_column( Column(np.zeros(len(event_table), dtype=float), name='n_peaks') ) event_table.add_column( Column(np.zeros(len(event_table), dtype=float), name='bin_delta_m') ) @@ -5217,13 +5470,14 @@ def refine_binary_events(events, companions, photometric_system, filter_name, event_table_df['companion_idx_list'] = empty_lists inputs = np.empty(multiples_lightcurves, dtype = object) + for i in range(len(grouped_comps.groups)): obj_id_L = grouped_comps.groups.keys[i][0] obj_id_S = grouped_comps.groups.keys[i][1] obj_id_L_S = (obj_id_L, obj_id_S) event_table_df['companion_idx_list'].loc[obj_id_L_S] = list(grouped_comps.groups[i]['companion_idx']) inputs[i] = [[event_table_df.loc[obj_id_L_S]], grouped_comps.groups[i].to_pandas(), obj_id_L, obj_id_S, - photometric_system, filter_name, red_law, save_phot, phot_dir, overwrite] + photometric_system, filter_name, red_law, save_phot, phot_dir, overwrite] if multi_proc: results = pool.starmap(one_lightcurve_analysis, inputs) @@ -5410,6 +5664,15 @@ def one_lightcurve_analysis(event_table_row, comp_table_rows, obj_id_L, obj_id_S else: raise Exception('one_lightcurve_analysis() only analyizes binary events') + event_table_row = event_table_row[0] + obj_id_L, obj_id_S = event_table_row.name + event_table_row = event_table_row.to_frame().T + event_table_row['obj_id_L'] = obj_id_L + event_table_row['obj_id_S'] = obj_id_S + event_table_row = Table.from_pandas(event_table_row) + + comp_table_rows = Table.from_pandas(comp_table_rows) + lightcurve_parameters = [] max_delta_m = np.nan if event_type == 'BSBL': @@ -5420,11 +5683,17 @@ def one_lightcurve_analysis(event_table_row, comp_table_rows, obj_id_L, obj_id_S for comp_idx_L in comp_idxs_L: global_comp_idx_L = comp_table_rows['companion_idx'][comp_idx_L] name = "L_{}_S_{}".format(obj_id_L, obj_id_S) + "compL_{}_compS_{}".format(global_comp_idx_L, global_comp_idx_S) - model_parameter_dict, _, _ = get_bsbl_lightcurve_parameters(event_table_row, comp_table_rows, int(comp_idx_L), int(comp_idx_S), + model_parameter_dict, _, _, model_name = lightcurves.get_bsbl_lightcurve_parameters(event_table_row, comp_table_rows, int(comp_idx_L), int(comp_idx_S), photometric_system, filter_name, red_law, event_id = 0) - model = bsbl_model_gen(model_parameter_dict) - param_dict = lightcurve_parameter_gen(model, model_parameter_dict, np.array([global_comp_idx_L, global_comp_idx_S]), - obj_id_L, obj_id_S, name, save_phot, phot_dir, overwrite) + mod_class = getattr(model, model_name) + try: + mod = mod_class(**model_parameter_dict) + ecc_ann_convergence_fail = False + except EccAnomalyError: + mod = None + ecc_ann_convergence_fail = True + param_dict = lightcurve_parameter_gen(mod, model_parameter_dict, np.array([global_comp_idx_L, global_comp_idx_S]), + obj_id_L, obj_id_S, ecc_ann_convergence_fail, name, save_phot, phot_dir, overwrite) lightcurve_dict = {'obj_id_L' : obj_id_L, 'obj_id_S' : obj_id_S, 'companion_id_L' : comp_table_rows['companion_idx'][comp_idx_L], 'companion_id_S' : comp_table_rows['companion_idx'][comp_idx_S], 'class' : event_type} lightcurve_parameters.append([param_dict, lightcurve_dict]) @@ -5438,9 +5707,17 @@ def one_lightcurve_analysis(event_table_row, comp_table_rows, obj_id_L, obj_id_S for comp_idx in range(len(comp_table_rows)): global_comp_idx = comp_table_rows['companion_idx'][comp_idx] name = "L_{}_S_{}".format(obj_id_L, obj_id_S) + "compL_{}".format(global_comp_idx) - model_parameter_dict, _, _ = get_psbl_lightcurve_parameters(event_table_row, comp_table_rows, comp_idx, photometric_system, filter_name, event_id = 0) - model = psbl_model_gen(model_parameter_dict) - param_dict = lightcurve_parameter_gen(model, model_parameter_dict, np.array([global_comp_idx]), obj_id_L, obj_id_S, name, save_phot, phot_dir, overwrite) + model_parameter_dict, _, _, model_name = lightcurves.get_psbl_lightcurve_parameters(event_table_row, comp_table_rows, comp_idx, + photometric_system, filter_name, event_id = 0) + mod_class = getattr(model, model_name) + try: + mod = mod_class(**model_parameter_dict) + ecc_ann_convergence_fail = False + except EccAnomalyError: + mod = None + ecc_ann_convergence_fail = True + param_dict = lightcurve_parameter_gen(mod, model_parameter_dict, np.array([global_comp_idx]), obj_id_L, obj_id_S, ecc_ann_convergence_fail, + name, save_phot, phot_dir, overwrite) lightcurve_dict = {'obj_id_L' : obj_id_L, 'obj_id_S' : obj_id_S, 'companion_id_L' : comp_table_rows['companion_idx'][comp_idx], 'companion_id_S' : np.nan, 'class' : event_type} lightcurve_parameters.append([param_dict, lightcurve_dict]) @@ -5452,9 +5729,17 @@ def one_lightcurve_analysis(event_table_row, comp_table_rows, obj_id_L, obj_id_S for comp_idx in range(len(comp_table_rows)): global_comp_idx = comp_table_rows['companion_idx'][comp_idx] name = "L_{}_S_{}".format(obj_id_L, obj_id_S) + "compS_{}".format(global_comp_idx) - model_parameter_dict, _, _ = get_bspl_lightcurve_parameters(event_table_row, comp_table_rows, comp_idx, photometric_system, filter_name, red_law, event_id = 0) - model = bspl_model_gen(model_parameter_dict) - param_dict = lightcurve_parameter_gen(model, model_parameter_dict, np.array([global_comp_idx]), obj_id_L, obj_id_S, name, save_phot, phot_dir, overwrite) + model_parameter_dict, _, _, model_name = lightcurves.get_bspl_lightcurve_parameters(event_table_row, comp_table_rows, comp_idx, + photometric_system, filter_name, red_law, event_id = 0) + mod_class = getattr(model, model_name) + try: + mod = mod_class(**model_parameter_dict) + ecc_ann_convergence_fail = False + except EccAnomalyError: + mod = None + ecc_ann_convergence_fail = True + param_dict = lightcurve_parameter_gen(mod, model_parameter_dict, np.array([global_comp_idx]), obj_id_L, obj_id_S, ecc_ann_convergence_fail, + name, save_phot, phot_dir, overwrite) lightcurve_dict = {'obj_id_L' : obj_id_L, 'obj_id_S' : obj_id_S, 'companion_id_L' : np.nan, 'companion_id_S' : comp_table_rows['companion_idx'][comp_idx], 'class' : event_type} lightcurve_parameters.append([param_dict, lightcurve_dict]) @@ -5504,7 +5789,7 @@ def model_param_dict2fits_header(model_parameter_dict, phot_dir, name): """ fits_file = phot_dir + '/' + name + '_phot.fits' - with fits.open(fits_file, 'update') as f: + with fits.open(fits_file, 'update', memmap=False) as f: hdr = f[0].header for key in list(model_parameter_dict.keys()): try: @@ -5514,7 +5799,7 @@ def model_param_dict2fits_header(model_parameter_dict, phot_dir, name): return -def lightcurve_parameter_gen(model, model_parameter_dict, comp_idxs, obj_id_L, obj_id_S, +def lightcurve_parameter_gen(model, model_parameter_dict, comp_idxs, obj_id_L, obj_id_S, ecc_ann_convergence_fail = False, name=None, save_phot=False, phot_dir=None, overwrite=False): """ Find the parameters @@ -5536,6 +5821,11 @@ def lightcurve_parameter_gen(model, model_parameter_dict, comp_idxs, obj_id_L, o obj_id_S : int Object id of the source associated with event + + ecc_ann_convergence_fail : bool, optional + If eccentric anomoly convergence has failed in BAGLE + saves default values for lightcurve. + Default is False. name : str or None, optional Name of fits file to be saved. @@ -5567,6 +5857,9 @@ def lightcurve_parameter_gen(model, model_parameter_dict, comp_idxs, obj_id_L, o param_dict = {'n_peaks' : 0, 'bin_delta_m' : np.nan, 'tE_sys' : np.nan, 'tE_primary' : np.nan, 'primary_t' : np.nan, 'avg_t' : np.nan, 'std_t' : np.nan, 'asymmetry' : np.nan, 'mp_rows' : [], 'used_lightcurve' : 0} + + if ecc_ann_convergence_fail: + return param_dict # Handles the case of modeling a triple source as a binary source, # But it's CO + CO + star and you're modeling the CO + CO pair (so no flux) @@ -5689,389 +5982,6 @@ def lightcurve_parameter_gen(model, model_parameter_dict, comp_idxs, obj_id_L, o return param_dict -def get_psbl_lightcurve_parameters(event_table, comp_table, comp_idx, photometric_system, filter_name, event_id = None): - """ - Find the parameters for PSBL_PhotAstrom_Par_Param7 from - event_table and comp_table. - - Parameters - ---------- - event_table : Astropy table - Table containing the events calculated from refine_events. - - comp_table : Astropy table - Table containing the companions calculated from refine_events. - - comp_idx : int - Index into the comp_table of the companion for which the psbl is being calculated. - - photometric_system : str - The name of the photometric system in which the filter exists. - - filter_name : str - The name of the filter in which to calculate all the - microlensing events. The filter name convention is set - in the global filt_dict parameter at the top of this module. - - event_id : float or None, optional - Corresponding event_id in event_table to companion id. - Default is None. - - Returns - ------- - psbl_parameter_dict : dict - Dictionary of the PSBL_PhotAstrom_Par_Param7 parameters - - obj_id_L : int - Object id of the lens associated with event - - obj_id_S : int - Object id of the source associated with event - - """ - obj_id_L = comp_table['obj_id_L'][comp_idx] - obj_id_S = comp_table['obj_id_S'][comp_idx] - - if event_id is None: - event_id = (np.where(np.logical_and((event_table['obj_id_L'] == obj_id_L), (event_table['obj_id_S'] == obj_id_S)))[0])[0] - - L_coords = SkyCoord(l = event_table[event_id]['glon_L']*unit.degree, b = event_table[event_id]['glat_L']*unit.degree, - pm_l_cosb = event_table[event_id]['mu_lcosb_L']*unit.mas/unit.year, - pm_b = event_table[event_id]['mu_b_L']*unit.mas/unit.year, frame ='galactic') - S_coords = SkyCoord(l = event_table[event_id]['glon_S']*unit.degree, b = event_table[event_id]['glat_S']*unit.degree, - pm_l_cosb = event_table[event_id]['mu_lcosb_S']*unit.mas/unit.year, - pm_b = event_table[event_id]['mu_b_S']*unit.mas/unit.year, frame ='galactic') - raL = L_coords.icrs.ra.value # Lens R.A. - decL = L_coords.icrs.dec.value # Lens dec - mL1 = event_table[event_id]['mass_L'] # msun (Primary lens current mass) - mL2 = comp_table['mass'][comp_idx] # msun (Companion lens current mass) - t0 = event_table[event_id]['t0'] # mjd - xS0 = np.array([0, 0]) #arbitrary offset (arcsec) - beta = event_table[event_id]['u0']*event_table[event_id]['theta_E']#5.0 - muL = np.array([L_coords.icrs.pm_ra_cosdec.value, L_coords.icrs.pm_dec.value]) #lens proper motion mas/year - muS = np.array([S_coords.icrs.pm_ra_cosdec.value, S_coords.icrs.pm_dec.value]) #source proper motion mas/year - dL = event_table[event_id]['rad_L']*10**3 #Distance to lens - dS = event_table[event_id]['rad_S']*10**3 #Distance to source - sep = comp_table['sep'][comp_idx] #mas (separation between primary and companion) - alpha = comp_table['alpha'][comp_idx] - mag_src = event_table[event_id]['%s_%s_app_S' % (photometric_system, filter_name)] - b_sff = event_table[event_id]['f_blend_%s' % filter_name] #ASSUMES ALL BINARY LENSES ARE BLENDED - model_name = 'PSBL_PhotAstrom_Par_Param7' - - psbl_parameter_dict = {'raL': raL, 'decL': decL, 'mL1': mL1, 'mL2': mL2, - 't0': t0, 'xS0': xS0, 'beta': beta, 'muL': muL, - 'muS': muS, 'dL': dL, 'dS': dS, 'sep': sep, - 'alpha': alpha, 'mag_src': mag_src, 'b_sff': b_sff, - 'model': model_name} - return psbl_parameter_dict, obj_id_L, obj_id_S - - -def psbl_model_gen(psbl_parameter_dict): - """ - Generate psbl_photastrom_par_param1 model from parameter dict - - Parameters - ---------- - psbl_parameter_dict : dict - Dictionary of the PSBL_PhotAstrom_Par_Param7 parameters - - Returns - ------- - psbl model - """ - ########## - # Calculate binary model and photometry - ########## - raL = psbl_parameter_dict['raL'] # Lens R.A. - decL = psbl_parameter_dict['decL'] # Lens dec - mL1 = psbl_parameter_dict['mL1'] # msun (Primary lens current mass) - mL2 = psbl_parameter_dict['mL2'] # msun (Companion lens current mass) - t0 = psbl_parameter_dict['t0'] # mjd - xS0 = psbl_parameter_dict['xS0'] #arbitrary offset (arcsec) - beta = psbl_parameter_dict['beta'] - muL = psbl_parameter_dict['muL'] #lens proper motion mas/year - muS = psbl_parameter_dict['muS'] #source proper motion mas/year - dL = psbl_parameter_dict['dL'] #Distance to lens - dS = psbl_parameter_dict['dS'] #Distance to source - sep = psbl_parameter_dict['sep'] #mas (separation between primary and companion) - alpha = psbl_parameter_dict['alpha'] - mag_src = psbl_parameter_dict['mag_src'] - b_sff = psbl_parameter_dict['b_sff'] #ASSUMES ALL BINARY LENSES ARE BLENDED - - psbl = model.PSBL_PhotAstrom_Par_Param7(mL1, mL2, t0, xS0[0], xS0[1], - beta, muL[0], muL[1], muS[0], muS[1], dL, dS, - sep, alpha, [b_sff], [mag_src], - raL=raL, decL=decL, - root_tol = 0.00000001) - return psbl - - -def get_bspl_lightcurve_parameters(event_table, comp_table, comp_idx, photometric_system, filter_name, red_law, event_id = None): - """ - Find the parameters for BSPL_PhotAstrom_Par_Param1 from - event_table and comp_table. - - Parameters - ---------- - event_table : Astropy table - Table containing the events calculated from refine_events. - - comp_table : Astropy table - Table containing the companions calculated from refine_events. - - comp_idx : int - Index into the comp_table of the companion for which the psbl is being calculated. - - photometric_system : str - The name of the photometric system in which the filter exists. - - filter_name : str - The name of the filter in which to calculate all the - microlensing events. The filter name convention is set - in the global filt_dict parameter at the top of this module. - - red_law : str - Redenning law - - event_id : float or None, optional - Corresponding event_id in event_table to companion id - - Returns - ------- - bspl_parameter_dict : dict - Dictionary of the BSPL_PhotAstrom_Par_Param1 parameters - - obj_id_L : int - Object id of the lens associated with event - - obj_id_S : int - Object id of the source associated with event - - """ - obj_id_L = comp_table['obj_id_L'][comp_idx] - obj_id_S = comp_table['obj_id_S'][comp_idx] - - if event_id is None: - event_id = (np.where(np.logical_and((event_table['obj_id_L'] == obj_id_L), (event_table['obj_id_S'] == obj_id_S)))[0])[0] - L_coords = SkyCoord(l = event_table[event_id]['glon_L']*unit.degree, b = event_table[event_id]['glat_L']*unit.degree, - pm_l_cosb = event_table[event_id]['mu_lcosb_L']*unit.mas/unit.year, - pm_b = event_table[event_id]['mu_b_L']*unit.mas/unit.year, frame ='galactic') - S_coords = SkyCoord(l = event_table[event_id]['glon_S']*unit.degree, b = event_table[event_id]['glat_S']*unit.degree, - pm_l_cosb = event_table[event_id]['mu_lcosb_S']*unit.mas/unit.year, - pm_b = event_table[event_id]['mu_b_S']*unit.mas/unit.year, frame ='galactic') - f_i = filt_dict[photometric_system + '_' + filter_name][red_law] - abs_mag_sec = comp_table['m_%s_%s' % (photometric_system, filter_name)][comp_idx] - - raL = L_coords.icrs.ra.value # Lens R.A. - decL = L_coords.icrs.dec.value # Lens dec - mL = event_table[event_id]['mass_L'] # msun (Lens current mass) - t0 = event_table[event_id]['t0'] # mjd - beta = event_table[event_id]['u0']*event_table[event_id]['theta_E']#5.0 - dL = event_table[event_id]['rad_L']*10**3 #Distance to lens - dL_dS = dL/(event_table[event_id]['rad_S']*10**3) #Distance to lens/Distance to source - xS0 = np.array([0, 0]) #arbitrary offset (arcsec) - muL_E = L_coords.icrs.pm_ra_cosdec.value #lens proper motion mas/year - muL_N = L_coords.icrs.pm_dec.value #lens proper motion mas/year - muS_E = S_coords.icrs.pm_ra_cosdec.value #lens proper motion mas/year - muS_N = S_coords.icrs.pm_dec.value #lens proper motion mas/year - sep = comp_table['sep'][comp_idx] #mas (separation between primary and companion) - alpha = comp_table['alpha'][comp_idx] - mag_src_sec = calc_app_mag(event_table[event_id]['rad_S'], abs_mag_sec, event_table[event_id]['exbv_S'], f_i) - mag_src_pri = binary_utils.subtract_magnitudes(event_table[event_id]['%s_%s_app_S' % (photometric_system, filter_name)], mag_src_sec) - b_sff = event_table[event_id]['f_blend_%s' % filter_name] #ASSUMES THAT SOURCE BINARIES ARE BLENDED - model_name = 'BSPL_PhotAstrom_Par_Param1' - - bspl_parameter_dict = {'model': model_name, 'raL': raL, 'decL': decL, 'mL': mL, - 't0': t0, 'xS0': xS0, 'beta': beta, - 'muL_E': muL_E, 'muL_N': muL_N, 'muS_E': muS_E, 'muS_N': muS_N, - 'dL': dL, 'dL_dS': dL_dS, 'sep': sep, - 'alpha': alpha, 'mag_src_pri': mag_src_pri, 'mag_src_sec': mag_src_sec, - 'b_sff': b_sff} - - return bspl_parameter_dict, obj_id_L, obj_id_S - -def bspl_model_gen(bspl_parameter_dict): - """ - Generate bspl_photastrom_par_param1 model from parameter dict - - Parameters - ---------- - bspl_parameter_dict : dict - Dictionary of the BSPL_PhotAstrom_Par_Param1 parameters - - Returns - ------- - bspl model - """ - ########## - # Calculate binary model and photometry - ########## - raL = bspl_parameter_dict['raL'] # Lens R.A. - decL = bspl_parameter_dict['decL'] # Lens dec - mL = bspl_parameter_dict['mL'] # msun (Lens current mass) - t0 = bspl_parameter_dict['t0'] # mjd - beta = bspl_parameter_dict['beta'] - dL = bspl_parameter_dict['dL'] #Distance to lens - dL_dS = bspl_parameter_dict['dL_dS'] #Distance to lens/Distance to source - xS0 = bspl_parameter_dict['xS0'] #arbitrary offset (arcsec) - muL_E = bspl_parameter_dict['muL_E'] #lens proper motion mas/year - muL_N = bspl_parameter_dict['muL_N'] #lens proper motion mas/year - muS_E = bspl_parameter_dict['muS_E'] #lens proper motion mas/year - muS_N = bspl_parameter_dict['muS_N'] #lens proper motion mas/year - sep = bspl_parameter_dict['sep'] #mas (separation between primary and companion) - alpha = bspl_parameter_dict['alpha'] - mag_src_sec = bspl_parameter_dict['mag_src_sec'] - mag_src_pri = bspl_parameter_dict['mag_src_pri'] - b_sff = bspl_parameter_dict['b_sff'] #ASSUMES ALL BINARY LENSES ARE BLENDED - - bspl = model.BSPL_PhotAstrom_Par_Param1(mL, t0, beta, dL, dL_dS, - xS0[0], xS0[1], muL_E, muL_N, muS_E, muS_N, - sep, alpha, [mag_src_pri], [mag_src_sec], [b_sff], - raL=raL, decL=decL) - - return bspl - -def get_bsbl_lightcurve_parameters(event_table, comp_table, comp_idx_L, comp_idx_S, photometric_system, filter_name, red_law, event_id = None): - """ - Find the parameters for BSPL_PhotAstrom_Par_Param2 from - event_table and comp_table. - - Parameters - ---------- - event_table : Astropy table - Table containing the events calculated from refine_events. - - comp_table : Astropy table - Table containing the companions calculated from refine_events. - - comp_idx_L : int - Index into the comp_table of the lens companion for which the model is being calculated. - - comp_idx_S : int - Index into the comp_table of the source companion for which the model is being calculated. - - photometric_system : str - The name of the photometric system in which the filter exists. - - filter_name : str - The name of the filter in which to calculate all the - microlensing events. The filter name convention is set - in the global filt_dict parameter at the top of this module. - - red_law : str - Redenning law - - event_id : float or None, optional - Corresponding event_id in event_table to companion id. - Default is None. - - Returns - ------- - bsbl_parameter_dict : dict - Dictionary of the BSBL_PhotAstrom_Par_Param2 parameters - - obj_id_L : int - Object id of the lens associated with event - - obj_id_S : int - Object id of the source associated with event - - """ - obj_id_L = comp_table['obj_id_L'][comp_idx_L] # This is equivalent to doing comp_idx_S - obj_id_S = comp_table['obj_id_S'][comp_idx_S] - - if event_id is None: - event_id = (np.where(np.logical_and((event_table['obj_id_L'] == obj_id_L), (event_table['obj_id_S'] == obj_id_S)))[0])[0] - - L_coords = SkyCoord(l = event_table[event_id]['glon_L']*unit.degree, b = event_table[event_id]['glat_L']*unit.degree, - pm_l_cosb = event_table[event_id]['mu_lcosb_L']*unit.mas/unit.year, - pm_b = event_table[event_id]['mu_b_L']*unit.mas/unit.year, frame ='galactic') - S_coords = SkyCoord(l = event_table[event_id]['glon_S']*unit.degree, b = event_table[event_id]['glat_S']*unit.degree, - pm_l_cosb = event_table[event_id]['mu_lcosb_S']*unit.mas/unit.year, - pm_b = event_table[event_id]['mu_b_S']*unit.mas/unit.year, frame ='galactic') - - f_i = filt_dict[photometric_system + '_' + filter_name][red_law] - abs_mag_sec = comp_table['m_%s_%s' % (photometric_system, filter_name)][comp_idx_S] - - raL = L_coords.icrs.ra.value # Lens R.A. - decL = L_coords.icrs.dec.value # Lens dec - mLp = event_table[event_id]['mass_L'] # msun (Lens current mass) - mLs = comp_table['mass'][comp_idx_L] # msun (Companion lens current mass) - t0 = event_table[event_id]['t0'] # mjd - beta = event_table[event_id]['u0']*event_table[event_id]['theta_E']#5.0 - dL = event_table[event_id]['rad_L']*10**3 #Distance to lens - dS = event_table[event_id]['rad_S']*10**3 #Distance to source - xS0_E = 0.0 #arbitrary offset (arcsec) - xS0_N = 0.0 #arbitrary offset (arcsec) - muL_E = L_coords.icrs.pm_ra_cosdec.value #lens proper motion mas/year - muL_N = L_coords.icrs.pm_dec.value #lens proper motion mas/year - muS_E = S_coords.icrs.pm_ra_cosdec.value #lens proper motion mas/year - muS_N = S_coords.icrs.pm_dec.value #lens proper motion mas/year - sepL = comp_table['sep'][comp_idx_L] #mas (separation between primary and companion) - alphaL = comp_table['alpha'][comp_idx_L] # PA of binary on the sky - sepS = comp_table['sep'][comp_idx_S] #mas (separation between primary and companion) - alphaS = comp_table['alpha'][comp_idx_S] # PA of source binary on the sky - mag_src_sec = calc_app_mag(event_table[event_id]['rad_S'], abs_mag_sec, event_table[event_id]['exbv_S'], f_i) - mag_src_pri = binary_utils.subtract_magnitudes(event_table[event_id]['%s_%s_app_S' % (photometric_system, filter_name)], mag_src_sec) - b_sff = event_table[event_id]['f_blend_%s' % filter_name] #ASSUMES THAT SOURCE BINARIES ARE BLENDED - model_name = 'BSBL_PhotAstrom_Par_Param2' - - bsbl_parameter_dict = {'model': model_name, 'raL': raL, 'decL': decL, 'mLp': mLp, 'mLs': mLs, - 't0': t0, 'xS0_E': xS0_E, 'xS0_N': xS0_N, 'beta': beta, - 'muL_E': muL_E, 'muL_N': muL_N, 'muS_E': muS_E, 'muS_N': muS_N, - 'dL': dL, 'dS': dS, 'sepL': sepL, 'alphaL': alphaL, - 'sepS': sepS, 'alphaS': alphaS, - 'mag_src_pri': mag_src_pri, 'mag_src_sec': mag_src_sec, - 'b_sff': b_sff} - - return bsbl_parameter_dict, obj_id_L, obj_id_S - -def bsbl_model_gen(bsbl_parameter_dict): - """ - Generate bsbl_photastrom_par_param1 model from parameter dict - - Parameters - ---------- - bspl_parameter_dict : dict - Dictionary of the BSBL_PhotAstrom_Par_Param1 parameters - - Returns - ------- - bsbl model - """ - ########## - # Calculate binary model and photometry - ########## - raL = bsbl_parameter_dict['raL'] # Lens R.A. - decL = bsbl_parameter_dict['decL'] # Lens dec - mLp = bsbl_parameter_dict['mLp'] # msun (Lens current mass) - mLs = bsbl_parameter_dict['mLs'] # msun (Lens companion current mass) - t0 = bsbl_parameter_dict['t0'] # mjd - beta = bsbl_parameter_dict['beta'] - dL = bsbl_parameter_dict['dL'] #Distance to lens - dS = bsbl_parameter_dict['dS'] #Distance to source - xS0_E = bsbl_parameter_dict['xS0_E'] #arbitrary offset (arcsec) - xS0_N = bsbl_parameter_dict['xS0_N'] #arbitrary offset (arcsec) - muL_E = bsbl_parameter_dict['muL_E'] #lens proper motion mas/year - muL_N = bsbl_parameter_dict['muL_N'] #lens proper motion mas/year - muS_E = bsbl_parameter_dict['muS_E'] #lens proper motion mas/year - muS_N = bsbl_parameter_dict['muS_N'] #lens proper motion mas/year - sepL = bsbl_parameter_dict['sepL'] #mas (separation between primary and companion) - alphaL = bsbl_parameter_dict['alphaL'] - sepS = bsbl_parameter_dict['sepS'] #mas (separation between primary and companion) - alphaS = bsbl_parameter_dict['alphaS'] - mag_src_sec = bsbl_parameter_dict['mag_src_sec'] - mag_src_pri = bsbl_parameter_dict['mag_src_pri'] - b_sff = bsbl_parameter_dict['b_sff'] #ASSUMES ALL BINARY LENSES ARE BLENDED - - bsbl = model.BSBL_PhotAstrom_Par_Param2(mLp, mLs, t0, xS0_E, xS0_N, - beta, muL_E, muL_N, muS_E, muS_N, - dL, dS, sepL, alphaL, sepS, alphaS, - mag_src_pri, mag_src_sec, b_sff, - raL=raL, decL=decL, - root_tol=1e-4) - return bsbl - ################################################################## ############ Reading/writing and format functions ############### ################################################################## @@ -6357,7 +6267,8 @@ def einstein_radius(M, d_L, d_S): ------- Einstein radius, in mas """ - return 2.85 * M ** 0.5 * (1 / d_L - 1 / d_S) ** 0.5 + inv_dist_diff = (1 / (d_L*unit.kpc) - 1 / (d_S*unit.kpc)) + return (unit.rad*(np.sqrt((4*const.G/(const.c**2)) * M*unit.M_sun * inv_dist_diff))).to('mas').value def calc_sph_motion(vx, vy, vz, r, b, l): @@ -6555,28 +6466,3 @@ def calc_ext(E, f): return m_E -def get_Alambda_AKs(red_law_name, lambda_eff): - """ - Get Alambda/AKs. NOTE: this doesn't work for every law in SPISEA! - Naming convention is not consistent. Change SPISEA or add if statements? - - Parameters - ---------- - red_law_name : str - The name of the reddening law - lambda_eff : float - Wavelength in microns - - Returns - ------- - Alambda_AKs : float - Alambda/AKs - - """ - red_law_class = getattr(reddening, 'RedLaw' + red_law_name) - red_law = red_law_class() - red_law_method = getattr(red_law, red_law_name) - Alambda_AKs = red_law_method(lambda_eff, 1) - - return Alambda_AKs - diff --git a/popsycle/table_utils.py b/popsycle/table_utils.py new file mode 100644 index 0000000..a2f41ed --- /dev/null +++ b/popsycle/table_utils.py @@ -0,0 +1,278 @@ +import pandas as pd +import numpy as np +from astropy.table import Table, vstack, Column +import os + +def combine_re_popsycle_tables(base_dir, filter_str, red_law, modifier_str = '', with_multiples = False): + """ + Combine together tables from different fields in PopSyCLE after refine events + and adds a field id column. + + Inputs + ------ + base_dir : str + Base directory of field folders. + (example : /g3/PopSyCLE_sims/roman_v2025/). + + filter_str : str + Filter string (i.e. 'ubv_I', 'multi_filt') + + red_law : str + Reddening law (i.e. Damineli16) + + modifier_str : str, Optional + String after field name (i.e. '_0.1_bhb_frac'). + Default is ''. + + with_multiples : bool, Optional + Also combines companions table if True. + + Returns + ------- + events : Astropy table + Combined PopSyCLE event table. + + companions : Astropy table, Optional + Combined PopSyCLE companions table. + Only returns if with_multiples == True. + + """ + fields = os.listdir(base_dir) + + event_tables = [] + + if with_multiples: + companion_tables = [] + for field in fields: + try: + base_dir_field = base_dir + '{}/{}'.format(field, field) + modifier_str + events = Table.read(base_dir_field + '_refined_events_' + filter_str + '_' + red_law + '.fits') + events.add_column( Column((np.repeat(field, len(events))), name='field_id') ) + event_tables.append(events) + + if with_multiples: + companions = Table.read(base_dir_field + '_refined_events_' + filter_str + '_' + red_law + '_companions.fits') + companions.add_column( Column((np.repeat(field, len(companions))), name='field_id') ) + companion_tables.append(companions) + + except FileNotFoundError: + print('Files not found for field folder: ', field) + pass + + events = vstack(event_tables) + if with_multiples: + companions = vstack(companion_tables) + return events, companions + else: + return events + +def combine_rbe_popsycle_tables(base_dir, filter_str, red_law, modifier_str = ''): + """ + Combine together tables from different fields in PopSyCLE after refine binary events + and adds a field id column. + + Inputs + ------ + base_dir : str + Base directory of field folders. + (example : /g3/PopSyCLE_sims/roman_v2025/). + + filter_str : str + Filter string (i.e. 'ubv_I', 'multi_filt') + + red_law : str + Reddening law (i.e. Damineli16) + + modifier_str : str, Optional + String after field name (i.e. '_0.1_bhb_frac'). + Default is ''. + + Returns + ------- + events : Astropy table + Combined PopSyCLE event table. + + companions : Astropy table + Combined PopSyCLE companions table. + + multi_peak : Astropy table + Combined PopSyCLE table of the multi peaks. + + lcs : Astropy table + Combined PopSyCLE table of lightcurves. + + """ + fields = os.listdir(base_dir) + + event_tables = [] + companion_tables = [] + multi_peak_tables = [] + lc_tables = [] + for field in fields: + try: + base_dir_field = base_dir + '{}/{}'.format(field, field) + modifier_str + events = Table.read(base_dir_field + '_refined_events_' + filter_str + '_' + red_law + '_rb.fits') + companions = Table.read(base_dir_field + '_refined_events_' + filter_str + '_' + red_law + '_companions_rb.fits') + multi_peak = Table.read(base_dir_field + '_refined_events_' + filter_str + '_' + red_law + '_companions_rb_multi_peaks.fits') + lcs = Table.read(base_dir_field + '_refined_events_' + filter_str + '_' + red_law + '_rb_lightcurves.fits') + + events.add_column( Column((np.repeat(field, len(events))), name='field_id') ) + companions.add_column( Column((np.repeat(field, len(companions))), name='field_id') ) + multi_peak.add_column( Column((np.repeat(field, len(multi_peak))), name='field_id') ) + lcs.add_column( Column((np.repeat(field, len(lcs))), name='field_id') ) + + event_tables.append(events) + companion_tables.append(companions) + multi_peak_tables.append(multi_peak) + lc_tables.append(lcs) + except FileNotFoundError: + print('Files not found for field folder: ', field) + pass + + events = vstack(event_tables) + companions = vstack(companion_tables) + multi_peak = vstack(multi_peak_tables) + lcs = vstack(lc_tables) + + return events, companions, multi_peak, lcs + +def trim_popsycle_tables(idx, event_tab, comps_tab, lcurv_tab): + """ + Trim down PopSyCLE event, companions, and lightcurve tables + to contain only those with the specified indices=idx. + + The companions and lightcurve tables are matched against the + trimmed event table's obj_id_L + obj_id_S. + + Inputs + ------ + idx : array, ints + indices returned from a where statement. + event_tab : astropy.table + PopSyCLE events table. + comps_tab : astropy.table + PopSyCLE companions table. + lcurve_tab : astropy.table + PopSyCLE lightcurves table. + + This code was generated by Gemini AI and edited by J. Lu. + """ + import pandas as pd + import numpy as np + + # --- Step 1: Convert to pandas DataFrames and apply initial trim --- + # Convert the tables to pandas DataFrames for faster operations + event_df = event_tab.to_pandas() + comps_df = comps_tab.to_pandas() + lcurv_df = lcurv_tab.to_pandas() + + # Apply the initial index trim + event_df_t = event_df.iloc[idx].copy() # Use iloc for index-based selection + + # --- Step 2: Create a DataFrame of the keys to search for --- + # This is the list of unique (obj_id_L, obj_id_S) pairs we are interested in + search_keys = event_df_t[['obj_id_L', 'obj_id_S']].drop_duplicates().copy() + + # --- Step 3: Vectorized filtering using merge or isin() (Recommended) --- + # The most efficient way is to create a combined key and use isin() + # Create a combined key column for comparison (e.g., 'key_L_S') + # Convert columns to string and concatenate them + search_keys['key_L_S'] = search_keys['obj_id_L'].astype(str) + '_' + search_keys['obj_id_S'].astype(str) + comps_df['key_L_S'] = comps_df['obj_id_L'].astype(int).astype(str) + '_' + comps_df['obj_id_S'].astype(int).astype(str) + lcurv_df['key_L_S'] = lcurv_df['obj_id_L'].astype(int).astype(str) + '_' + lcurv_df['obj_id_S'].astype(int).astype(str) + + # Filter the tables using the isin() method on the combined key + print(len(comps_df), len(lcurv_df)) + comps_df_t = comps_df[comps_df['key_L_S'].isin(search_keys['key_L_S'])] + lcurv_df_t = lcurv_df[lcurv_df['key_L_S'].isin(search_keys['key_L_S'])] + + # --- Step 4 (Optional): Clean up and convert back to Astropy Table if needed --- + # Remove the temporary key column + comps_df_t = comps_df_t.drop(columns=['key_L_S']) + lcurv_df_t = lcurv_df_t.drop(columns=['key_L_S']) + + # If the result must be an Astropy Table: + # from astropy.table import Table + event_tab_t = Table.from_pandas(event_df_t) + comps_tab_t = Table.from_pandas(comps_df_t) + lcurv_tab_t = Table.from_pandas(lcurv_df_t) + + return event_tab_t, comps_tab_t, lcurv_tab_t + + +def cut_Mruns(t_prim, t_comp_rb, t_comp_rb_mp, min_mag, delta_m_cut, u0_cut, photometric_system, filter_name, S_LSN): + """ + Make observational cuts on PopSyCLE runs with multiple systems + + Parameters + ---------- + t_prim : Astropy table + Events table from refine_binary_events. + Must contain 'observable_n_peaks' column. + + t_comp_rb : Astropy table + Companion table from refine_binary_events. + + t_comp_rb_mp : Astropy table + Multi peak table from refine binary events + (each row corresponds to a peak in a lightcurve). + + min_mag : float + Minimum baseline or source magnitude (specified by S_LSN). + + delta_m_cut : float or None. + Minimum bump magnitude. + + u0_cut : float + Maximum u0. + + photometric_system : str + Photometric system when cutting on min_mag. + + filter_name : str + Filter name used when cutting on min_mag and delta_m_cut. + + S_LSN : str + 'S' for source mag cut or 'LSN' for baseline magnitude cut. + + Returns + ------- + t_both_mcut : Astropy table + Table with specified observational cuts. + + t_both_mcut_one_peak : Astropy table + Table with specified observational cuts and only single peaked events. + + t_multiples_mcut_multi_peak : Astropy table + Table with specified observational cuts and only multipeaked events + containing a multiple system. + """ + #S_LSN is source or baseline mag cut + if S_LSN == 'S': + mag_cut = t_prim['{}_{}_app_S'.format(photometric_system, filter_name)] <= min_mag + elif S_LSN == 'LSN': + mag_cut = t_prim['{}_{}_app_LSN'.format(photometric_system, filter_name)] <= min_mag + + u0_cut = np.abs(t_prim['u0']) < u0_cut + + binary_filt = (t_prim['isMultiple_L'] == 1) | (t_prim['isMultiple_S'] == 1) + single_filt = (t_prim['isMultiple_L'] == 0) & (t_prim['isMultiple_S'] == 0) + assert(len(t_prim) == (sum(binary_filt) + sum(single_filt))) + + if delta_m_cut is not None: + delta_m_cut = ((t_prim['bin_delta_m'] > 0.1) & binary_filt) | ((t_prim['delta_m_{}'.format(filter_name)] > 0.1) & single_filt) + total_cut = mag_cut & u0_cut & delta_m_cut + else: + total_cut = mag_cut & u0_cut + + t_both_mcut = t_prim[total_cut] + binary_filt_cut = (t_both_mcut['isMultiple_L'] == 1) | (t_both_mcut['isMultiple_S'] == 1) + single_filt_cut = (t_both_mcut['isMultiple_L'] == 0) & (t_both_mcut['isMultiple_S'] == 0) + assert(len(t_both_mcut) == (sum(binary_filt_cut) + sum(single_filt_cut))) + + t_mult_mcut_no_peaks = t_both_mcut[binary_filt_cut & (t_both_mcut['observable_n_peaks'] == 0)] + t_both_mcut_one_peak = t_both_mcut[(binary_filt_cut & (t_both_mcut['observable_n_peaks'] == 1)) | single_filt_cut] + t_multiples_mcut_multi_peak = t_both_mcut[binary_filt_cut & (t_both_mcut['observable_n_peaks'] > 1)] + assert(len(t_both_mcut) == (len(t_mult_mcut_no_peaks) + len(t_both_mcut_one_peak) + len(t_multiples_mcut_multi_peak))) + + return t_both_mcut, t_both_mcut_one_peak, t_multiples_mcut_multi_peak \ No newline at end of file diff --git a/popsycle/tests/test_lightcurves.py b/popsycle/tests/test_lightcurves.py index 183c80c..d6c9ce8 100644 --- a/popsycle/tests/test_lightcurves.py +++ b/popsycle/tests/test_lightcurves.py @@ -26,9 +26,13 @@ def test_get_bagle_model_list(): photom_sys, filter_name, red_law, n_multi_proc=n_multi_proc) - assert isinstance(model_list[0], model.PSBL_PhotAstrom_Par_Param7) + assert isinstance(model_list[0], model.PSBL_PhotAstrom_Par_EllOrbs_Param7) assert len(model_list) == len(events_tab) return +#FIXME Add test for lightcurve production +def test_psbl_multi_lightcurve(): + pass + diff --git a/popsycle/tests/test_synthetic.py b/popsycle/tests/test_synthetic.py index 0ceea6b..3ad0544 100755 --- a/popsycle/tests/test_synthetic.py +++ b/popsycle/tests/test_synthetic.py @@ -52,7 +52,7 @@ def galaxia(): return output_root -@pytest.fixture(name = 'galaxia', scope="module") +@pytest.fixture(name = 'galaxia', scope="session") def galaxia_fixture(): return galaxia() @@ -82,7 +82,7 @@ def srun_galaxia(galaixa): return output_root -@pytest.fixture(name = 'srun_galaxia', scope="module") +@pytest.fixture(name = 'srun_galaxia', scope="session") def srun_galaxia_fixture(galaxia): return srun_galaxia(galaxia) @@ -105,7 +105,7 @@ def srun_popsyn(srun_galaxia): return output_root -@pytest.fixture(name = 'srun_popsyn', scope="module") +@pytest.fixture(name = 'srun_popsyn', scope="session") def srun_popsyn_fixture(srun_galaxia): return srun_popsyn(srun_galaxia) @@ -127,7 +127,7 @@ def srun_calc_events(srun_popsyn): return output_root -@pytest.fixture(name = 'srun_calc_events', scope="module") +@pytest.fixture(name = 'srun_calc_events', scope="session") def srun_calc_events_fixture(srun_popsyn): return srun_calc_events(srun_popsyn) @@ -137,8 +137,7 @@ def srun_refine_events(srun_calc_events): input_root = srun_calc_events synthetic.refine_events(input_root=input_root, - filter_name='I', - photometric_system='ubv', + filter_dict={'ubv':['I']}, red_law='Damineli16', overwrite=True, output_file='default') @@ -147,7 +146,7 @@ def srun_refine_events(srun_calc_events): return output_root -@pytest.fixture(name = 'srun_refine_events', scope="module") +@pytest.fixture(name = 'srun_refine_events', scope="session") def srun_refine_events_fixture(srun_calc_events): return srun_refine_events(srun_calc_events) @@ -175,7 +174,7 @@ def mrun_galaxia(galaixa): return output_root -@pytest.fixture(name = 'mrun_galaxia', scope="module") +@pytest.fixture(name = 'mrun_galaxia', scope="session") def mrun_galaxia_fixture(galaxia): return mrun_galaxia(galaxia) @@ -201,7 +200,7 @@ def mrun_popsyn(mrun_galaxia): return output_root -@pytest.fixture(name = 'mrun_popsyn', scope="module") +@pytest.fixture(name = 'mrun_popsyn', scope="session") def mrun_popsyn_fixture(mrun_galaxia): return mrun_popsyn(mrun_galaxia) @@ -225,7 +224,7 @@ def mrun_calc_events(mrun_popsyn): return output_root -@pytest.fixture(name = 'mrun_calc_events', scope="module") +@pytest.fixture(name = 'mrun_calc_events', scope="session") def mrun_calc_events_fixture(mrun_popsyn): return mrun_calc_events(mrun_popsyn) @@ -235,8 +234,7 @@ def mrun_refine_events(mrun_calc_events): input_root = mrun_calc_events synthetic.refine_events(input_root=input_root, - filter_name='I', - photometric_system='ubv', + filter_dict={'ubv':['I']}, red_law='Damineli16', hdf5_file_comp=input_root + '_companions.h5', overwrite=True, @@ -247,7 +245,7 @@ def mrun_refine_events(mrun_calc_events): return output_root -@pytest.fixture(name = 'mrun_refine_events', scope="module") +@pytest.fixture(name = 'mrun_refine_events', scope="session") def mrun_refine_events_fixture(mrun_calc_events): return mrun_refine_events(mrun_calc_events) @@ -265,7 +263,7 @@ def mrun_refine_binary(mrun_refine_events): return output_root -@pytest.fixture(name = 'mrun_refine_binary', scope="module") +@pytest.fixture(name = 'mrun_refine_binary', scope="session") def mrun_refine_binary_fixture(mrun_refine_events): return mrun_refine_binary(mrun_refine_events) @@ -1203,15 +1201,15 @@ def test_no_nan_companions(mrun_popsyn): def test_single_CO_frac(srun_popsyn): """ Checks that the CO fraction of objects greater than 0.1 Msun - is about 9.1% + is about 10.6% """ test_hdf5 = h5py.File(srun_popsyn + '.h5', 'r') lower_mass_cutoff = 0.1 #Msun CO, total, CO_frac = calc_CO_frac_mass_cutoff(test_hdf5, lower_mass_cutoff) - precalc_CO_frac = 0.09138 - precalc_CO_number = 23173 - precalc_total_number = 253583 + precalc_CO_frac = 0.1065 + precalc_CO_number = 27474 + precalc_total_number = 257884 precalc_error = precalc_CO_frac*np.sqrt((np.sqrt(precalc_CO_number)/precalc_CO_number)**2 + (np.sqrt(precalc_total_number)/precalc_total_number)**2) assert(np.abs(CO_frac - precalc_CO_frac) < precalc_error) @@ -1236,18 +1234,18 @@ def calc_CO_frac_mass_cutoff(hdf5_file, lower_mass_cutoff): def test_multiplicity_properties(mrun_popsyn): """ - Checks that the multiplicity fraction of objects > 0.5 Msun is about 52% + Checks that the multiplicity fraction of objects > 0.5 Msun is about 49% and that the minimum semimajor axis is greater than 10^-2 """ test_hdf5 = h5py.File(mrun_popsyn + '.h5', 'r') lower_mass_cutoff = 0.5 #Msun multiplicity_frac, multiples, total = calc_multiplicity_frac_mass_cutoff(test_hdf5, lower_mass_cutoff) - precalc_mult_frac = 0.5199 - precalc_mult_number = 23710 - precalc_total_number = 45606 + precalc_mult_frac = 0.4915 + precalc_mult_number = 24519 + precalc_total_number = 49885 precalc_error = precalc_mult_frac*np.sqrt((np.sqrt(precalc_mult_number)/precalc_mult_number)**2 + (np.sqrt(precalc_total_number)/precalc_total_number)**2) - + assert(np.abs(multiplicity_frac - precalc_mult_frac) < precalc_error) test_hdf5.close() @@ -1322,6 +1320,7 @@ def test_refine_binary_events_multiple_lightcurves(mrun_refine_binary): test_events = Table.read(mrun_refine_binary + '.fits') +@pytest.mark.xfail def test_refine_binary_events_psbl_lightcurve(): """ Generates a lightcurve that should have 4 peaks. @@ -1364,7 +1363,7 @@ def test_refine_binary_events_psbl_lightcurve(): plt.savefig(popsycle.__path__[0] + '/tests/data_test/psbl_4_peaks_example.png') return - +@pytest.mark.xfail def test_bspl_single_luminous_source_one_peak(): """ Makes sure that a BSPL event with one luminous source diff --git a/popsycle/utils.py b/popsycle/utils.py index 8083657..dee2cba 100755 --- a/popsycle/utils.py +++ b/popsycle/utils.py @@ -13,6 +13,8 @@ import copy import time import datetime +from spisea import reddening +from popsycle import ebf def add_precision64(input_array, power): @@ -472,11 +474,39 @@ def calc_centroid_shift(glat_S, glon_S, glat_N, glon_N, f_L, f_S, f_N, u): return delta_c_obs +def get_Alambda_AKs(red_law_name, lambda_eff): + """ + Get Alambda/AKs. NOTE: this doesn't work for every law in SPISEA! + Naming convention is not consistent. Change SPISEA or add if statements? + + Parameters + ---------- + red_law_name : str + The name of the reddening law + lambda_eff : float + Wavelength in microns + + Returns + ------- + Alambda_AKs : float + Alambda/AKs + + """ + red_law_class = getattr(reddening, 'RedLaw' + red_law_name) + red_law = red_law_class() + red_law_method = getattr(red_law, red_law_name) + Alambda_AKs = red_law_method(lambda_eff, 1) + + return Alambda_AKs + def calc_f(lambda_eff): """ Calculate that coefficient f that multiples E(B-V) to get the extinction in magnitudes + + lambda_eff : float + Effective wavelength of filter in microns """ B = get_Alambda_AKs('Damineli16', 0.445) V = get_Alambda_AKs('Damineli16', 0.551) @@ -712,3 +742,46 @@ def nan_to_zero(flux): flux = np.nan_to_num(flux) return flux + +def merge_ebf_filters(ebf_file1, ebf_file2, phot_sys1, phot_sys2): + """ + Merges together the ebf files for two sets of filters + generated with the SAME SEED and SAME ISOCHRONE GEN METHOD + (i.e. the same CMD form - stev.oapd.inaf.it/cgi-bin/cmd). + + Parameters + ---------- + ebf_file1 : str + File name of ebf file of one set of filters. + This will also be the output file with all mags. + + ebf_file2 : str + File name of ebf file of second set of filters. + + phot_sys1 : str + Name of phot sys of ebf_file1 - i.e. 'ubv'. + + phot_sys2 : str + Name of phot sys of ebf_file2 - i.e. 'sdss'. + """ + + ebf1 = ebf.read(ebf_file1, '/') + ebf2 = ebf.read(ebf_file2, '/') + + # lowercase since this is what galaxia ouputs + phot_sys1 = phot_sys1.lower() + phot_sys2 = phot_sys2.lower() + + if len(ebf1['age']) != len(ebf2['age']): #age chosen as arbitrary column + raise Exception('ebf files must be generated with the same seed and same isochrone method so stars match') + + mag_keys = [key for key in list(ebf2.keys()) if phot_sys2 in key] + for key in mag_keys: + ebf1[key] = ebf2[key] + + for key in mag_keys: + ebf.write(ebf_file1, '/' + i, ebf1[key], "a") + + return + +