{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Binary Star Simulation Interactive\n", "\n", "This interactive application simulates the orbit of two stars around each other, taking into account \n", "\n", "- the masses of the two stars,\n", "- the shape of and orientation of the orbit in space, and\n", "- the sizes and luminosities of the two stars\n", "\n", "to accurately model the orbit as well as the observable radial velocites and light curve of the binary star system as observed from Earth. **Note**: This code takes an approach for modelling binary stars outlined by Carroll and Ostlie in their *TwoStars* code, but it has been extensively optimized for the Python programming language.\n", "\n", "## Model versus Reality \n", "\n", "Remember that the model shown on the left hand side of the two stars and their orbit is supposed to be just that, a model. It is not something we actually observe, but rather an explanation for what we observe. **For example:** We might *observe* a particular point of light in the sky varying in brightness in a regular manner. We try to explain why this point of light varies in brightness using a *model* that assumes it is due to two stars orbiting each other resulting in one star eclipsing the other. We may never have a telescope powerful enough to resolve that point of light into two stars, but we can explain its variation in brightness over time using the model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Developed by Juan Cabanela starting June 19, 2018 and proceeding through the Summer of 2018\n", "#\n", "# This simulation is meant to allow students to discover how adjusting the parameters of a model of a stellar system\n", "# can allow use to model either radial velocity curves or light curves.\n", "#\n", "# This code started life as an extension of the Center of Mass interactive (by Sam Holen) but required the development\n", "# of a Python implementation of a binary star model (based on approach used in the TwoStars code from\n", "# Carrolll and Ostlie)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import display, HTML\n", "import numpy as np\n", "import ipywidgets as widgets\n", "import traitlets\n", "import pythreejs as p3j\n", "import bqplot as bq\n", "import tempNcolor as tc\n", "import starlib as star\n", "import number_formatting as nf" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## FUNCTIONS ##\n", "\n", "def ConfigBothStars(mass1, mass2):\n", " '''\n", " Determines the radii (in solar radii), temperature (in K), and hexcolor of the two stars assuming \n", " they are main sequence stars and returns that information. Does this by calling the ConfigStar \n", " function for both stars.\n", " '''\n", " (radius1, temp1, hexcolor1) = star.ConfigStar(mass1)\n", " (radius2, temp2, hexcolor2) = star.ConfigStar(mass2)\n", " \n", " return (radius1, temp1, hexcolor1, radius2, temp2, hexcolor2)\n", "\n", "\n", "def MS_update(change=None):\n", " '''\n", " Did the user switch back and forth between main sequence stars and not main sequence stars?\n", " '''\n", " global mass1, mass2, radius1, radius2, temp1, temp2\n", " \n", " MS_choice = change['new']\n", " if (MS_choice == MS_yes):\n", " radius1_slider.disabled = True\n", " radius2_slider.disabled = True\n", " temp1_slider.disabled = True\n", " temp2_slider.disabled = True\n", " radius1_readout.disabled = True\n", " radius2_readout.disabled = True\n", " temp1_readout.disabled = True\n", " temp2_readout.disabled = True \n", " else:\n", " radius1_slider.disabled = False\n", " radius2_slider.disabled = False\n", " temp1_slider.disabled = False\n", " temp2_slider.disabled = False\n", " radius1_readout.disabled = False\n", " radius2_readout.disabled = False\n", " temp1_readout.disabled = False\n", " temp2_readout.disabled = False\n", " \n", " # Set the values of the radius and temperature sliders based on mass using \n", " # a main sequence assumption (OK to do at transition between the two assumptions)\n", " (radius1_slider.value, temp1_slider.value, hexcolor1, \n", " radius2_slider.value, temp2_slider.value, hexcolor2) = ConfigBothStars(mass1_slider.value, mass2_slider.value)\n", "\n", " # Update the orbital parameters if necessary\n", " property_update()\n", " \n", " \n", "def property_update(change=None):\n", " '''\n", " This function updates the stellar properties (and orbital properties) and is meant to be used\n", " when there are changes to stellar properties as controled by various ipywidgets on screen.\n", " '''\n", " global bsm, mass1, mass2, radius1, radius2, temp1, temp2\n", " \n", " # First determine if this is a main sequence star or not and adjust behavior\n", " if (MS_selector.value == MS_yes):\n", " bsm.continuous_update = False # Turn off continuous updating to allow quick changing of several variables\n", " # determine radius and temperature of stars assuming main sequence realtionship\n", " # to mass (changes should automatically propogate to the binary star model object)\n", " (radius1_slider.value, temp1_slider.value, hexcolor1, \n", " radius2_slider.value, temp2_slider.value, hexcolor2) = ConfigBothStars(mass1_slider.value, mass2_slider.value)\n", " bsm.continuous_update = True # Turn on continuous updating\n", " \n", " # Force a binary star model update (which should update view as well)\n", " bsm.force_update()\n", " \n", " # If there is a collision, stop the phase changes, otherwise allow access to changing phase\n", " if (bsm.collision):\n", " phase_title.value = phase_title_collision\n", " phase_play.step = 0\n", " phase_play.disabled = True\n", " phase_slider.disabled = True\n", " else:\n", " # Make sure orbital phase can be adjusted\n", " phase_title.value = phase_title_default\n", " phase_play.step = 1\n", " phase_play.disabled = False\n", " phase_slider.disabled = False\n", "\n", " # Set luminosities\n", " L1_output.value = str(nf.SigFig((temp1_slider.value/star.Te_Sun)**4 * radius1_slider.value**2, 3))\n", " L2_output.value = str(nf.SigFig((temp2_slider.value/star.Te_Sun)**4 * radius2_slider.value**2, 3))\n", "\n", " # Revise orbital readouts\n", " P_output.value = \"{0:.1f}\".format(bsm.P)\n", " ap_output.value = \"{0:.1f}\".format(bsm.ap*AU2RSun)\n", " aa_output.value = \"{0:.1f}\".format(bsm.aa*AU2RSun)\n", "\n", " # Update the on screen label to indicate unreal scaling if necessary\n", " if (binary_view.multiplier > 1):\n", " sys_title.value = sys_title_scaling\n", " else:\n", " sys_title.value = sys_title_default\n", " \n", " # Perform the inclination update to force an update of the display\n", " inclination_update()\n", "\n", " \n", "def inclination_update(change=None):\n", " '''\n", " This function updates the star system's inclination to the plane of the sky and updates the graph shown\n", " '''\n", " global bsm\n", " \n", " # Retrieve updated radial velocity or light curves and then update figure\n", " # (in OO version of this code, the bsm should have updated automatically following the inclination angle change)\n", " if (fig_selector.value == rv_val):\n", " update_radvel_curve(bsm.radvel_info, bsm.orbit_info)\n", " else:\n", " update_light_curve(bsm.lc_info)\n", " \n", " \n", "def position_update(change=None):\n", " '''\n", " This function updates the two stars' phase line on whatever graph is shown. The viewer changes are handled\n", " automatically by linking the slider to the t_idx value in the binary_view object.\n", " '''\n", " global binary_view\n", " \n", " # Update the phase line in radial velocity or light curve\n", " if (fig_selector.value == rv_val):\n", " rv_phase_line.x = [bsm.radvel_info['phase'][phase_slider.value], bsm.radvel_info['phase'][phase_slider.value]]\n", " else:\n", " lc_phase_line.x = [bsm.lc_info['phase'][phase_slider.value], bsm.lc_info['phase'][phase_slider.value]]\n", "\n", "\n", "def scene_update(change=None):\n", " '''\n", " This function updates the notificaiton of the grid/orbit drawing if necessary.\n", " '''\n", " binary_view.reset_grid_and_orbit()\n", " \n", "def graph_update(change=None):\n", " '''\n", " This function switches which graph to plot\n", " '''\n", " global bsm, graph_fig\n", " \n", " # Check if graph exists, if it doesn't, create it.\n", " try:\n", " graph_fig\n", " except NameError:\n", " graph_fig = None\n", " \n", " if (fig_selector.value == rv_val):\n", " bsm.wipe_lc_info()\n", " bsm.set_radvel_info()\n", " new_fig = create_radvel_curve(bsm.radvel_info, bsm.orbit_info)\n", " else:\n", " bsm.wipe_radvel_info()\n", " bsm.set_lc_info()\n", " new_fig = create_light_curve(bsm.lc_info)\n", " \n", " if (graph_fig is None):\n", " graph_fig = new_fig\n", " else:\n", " graph_fig.marks = new_fig.marks\n", " graph_fig.axes = new_fig.axes\n", " graph_fig.title = new_fig.title\n", " graph_fig.layout = new_fig.layout\n", "\n", "\n", "def create_light_curve(light_curve):\n", " '''\n", " Initialize the entire light curve \n", " '''\n", " global lc_line, lc_phase_line\n", " \n", " # Set scales\n", " sc_x = bq.LinearScale()\n", " sc_y = bq.LinearScale()\n", "\n", " # Build the light curve\n", " lc_line = bq.Lines(x=light_curve['phase'], y=light_curve['F_norm'], scales={'x': sc_x, 'y': sc_y}, \n", " colors=['Black'])\n", " \n", " # Indicate the current phase\n", " x_phase = [light_curve['phase'][phase_slider.value], light_curve['phase'][phase_slider.value]]\n", " y_phase = [0, 1] \n", " lc_phase_line = bq.Lines(x=x_phase, y=y_phase, scales={'x': sc_x, 'y': sc_y}, \n", " colors=['Red'])\n", " \n", " # Setup axes and return figure\n", " ax_x = bq.Axis(scale=sc_x, label='Phase')\n", " ax_y = bq.Axis(scale=sc_y, orientation='vertical', label='Fraction of Maximum Flux')\n", " return bq.Figure(marks=[lc_line, lc_phase_line], axes=[ax_x, ax_y], title='Light Curve',\n", " layout=widgets.Layout(width=graph_width, height=graph_height, margin='5px 5px 5px 5px'))\n", "\n", "\n", "def update_light_curve(lc_info):\n", " '''\n", " Update the light curve \n", " '''\n", " global lc_line\n", " lc_line.x=lc_info['phase']\n", " lc_line.y=lc_info['F_norm']\n", "\n", " \n", "def create_radvel_curve(radvel_info, orbit_info):\n", " '''\n", " Initialize the entire radial velocity curve\n", " '''\n", " global star1_line, star2_line, rv_phase_line\n", " \n", " # Set up the scales\n", " sc_x = bq.LinearScale()\n", " sc_y = bq.LinearScale()\n", "\n", " # Indicate the current phase\n", " x_phase = [radvel_info['phase'][phase_slider.value], radvel_info['phase'][phase_slider.value]]\n", " \n", " # Scale to min/max velocity at all inclinations (assume no systemic radial velocity)\n", " maxval = max(-np.min(orbit_info['vx1']), -np.min(orbit_info['vx2']))\n", " minval = min(-np.max(orbit_info['vx1']), -np.max(orbit_info['vx2']))\n", " y_phase = [minval, maxval] \n", " rv_phase_line = bq.Lines(x=x_phase, y=y_phase, scales={'x': sc_x, 'y': sc_y}, \n", " colors=['Red'])\n", " \n", " # Draw the radial velocity curves\n", " star1_line = bq.Lines(x=radvel_info['phase'], y=radvel_info['v1r'], scales={'x': sc_x, 'y': sc_y},\n", " colors=['DarkOrange'], labels=['Star 1'], display_legend=True)\n", " star2_line = bq.Lines(x=radvel_info['phase'], y=radvel_info['v2r'], scales={'x': sc_x, 'y': sc_y},\n", " colors=['Blue'], labels=['Star 2'], display_legend=True)\n", " \n", " # Setup axes and return (initially invisible) figure\n", " ax_x = bq.Axis(scale=sc_x, label='Phase')\n", " ax_y = bq.Axis(scale=sc_y, orientation='vertical', label='Radial velocity (km/s)')\n", " ax_y.label_offset = '3.5em'\n", " return bq.Figure(marks=[star1_line, star2_line, rv_phase_line], axes=[ax_x, ax_y], title='Radial Velocity Curve',\n", " layout=widgets.Layout(width=graph_width, height=graph_height, margin='5px 5px 5px 5px'))\n", "\n", "\n", "def update_radvel_curve(radvel_info, orbit_info):\n", " '''\n", " Update the radial velocity curve \n", " '''\n", " global star1_line, star2_line, rv_phase_line\n", " \n", " star1_line.x=radvel_info['phase']\n", " star1_line.y=radvel_info['v1r']\n", " star2_line.x=radvel_info['phase']\n", " star2_line.y=radvel_info['v2r']\n", " \n", " # Rescale phase line limits if orbit changes\n", " maxval = max(-np.min(orbit_info['vx1']), -np.min(orbit_info['vx2']))\n", " minval = min(-np.max(orbit_info['vx1']), -np.max(orbit_info['vx2']))\n", " rv_phase_line.y = [minval, maxval] \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## INTERACTIVE/DISPLAY WIDGETS ##\n", "\n", "# Conversion constants\n", "RSun2AU = star.R_Sun/star.AU\n", "AU2RSun = 1/RSun2AU\n", "\n", "# Define constants\n", "min_mass = 0.2 # Minimum stellar mass in solar masses\n", "max_mass = 24 # Maximum stellar mass in solar masses\n", "mass_step = 0.1 # Step size for mass sliders in solar masses\n", "init_mass = 1 # Initial mass of both stars in solar masses\n", "\n", "min_radius = 0.2 # Minimum stellar radius in solar radii\n", "max_radius = 100 # Maximum stellar radius in solar radii\n", "radius_step = 0.1 # Step size for radius sliders in solar radii\n", "init_radius = 1 # Initial radius of both stars in solar radii\n", "\n", "min_temp = 3100 # Minimum stellar temperature in K\n", "max_temp = 40000 # Maximum stellar temperature in K\n", "temp_step = 10 # Step size for temperature sliders in K\n", "init_temp = int(star.Te_Sun/10)*10 # Initial temperature of both stars in K\n", "\n", "min_a = 0.005 # Minimum semimajor axis of stars in AU\n", "max_a = 1.5 # Maximum semimajor axis of stars in AU\n", "step_a = 0.005 # Step size for separation slider in AU\n", "init_a = 0.05 # Start off with the two stars close together\n", "\n", "init_incl = 0.0 # Initial inclination value (non-zero value avoids odd orientation of FOV at start)\n", "init_phi = 0 # Initial semimajor axis phase angle\n", "init_ecc = 0.2 # Initial orbital eccentricity\n", "\n", "view_factor = 1.25 # How many times the maximum distance to place the viewer\n", "N = 1000 # Number of time steps to use for orbit\n", "Na = 50 # Number of annuli to break up stars into for computing eclipse fraction\n", "Ntheta = 180 # Number of angular steps to break up stars into for computing eclipse fraction\n", "\n", "#\n", "# Define some widths to use throughout for layout of controls\n", "#\n", "\n", "# Set simulation size\n", "view_width = 350\n", "view_height = view_width\n", "\n", "# Initialize slider sizes\n", "EntireWidth = '1000px'\n", "SimWidth = '{0:.0f}px'.format(view_width)\n", "ControlColWidth = '450px'\n", "slider_width = '300px'\n", "slider_minwidth = '250px'\n", "readout_width = '70px'\n", "lum_width = '120px'\n", "inform_width = '200px'\n", "graph_width = '450px'\n", "graph_height = '300px'\n", "\n", "##\n", "## Create control for selecting Figure\n", "##\n", "rv_val = 'Radial Velocity Curve'\n", "lc_val = 'Light Curve'\n", "fig_selector = widgets.ToggleButtons(options=[rv_val, lc_val],\n", " value=rv_val,\n", " #description='Plot to Display:',\n", " #style = {'description_width': 'initial'},\n", " disabled=False,\n", " orientation='horizontal',\n", " layout=widgets.Layout(width=ControlColWidth,\n", " height='70px', \n", " overflow='visible')\n", " )\n", "\n", "\n", "##\n", "## Create control for selecting main sequence or not\n", "##\n", "MS_yes = 'yes'\n", "MS_yes_descript = 'Model stars as main sequence stars described solely by mass'\n", "MS_no = 'no'\n", "MS_no_descript = 'Allow non-main sequence stars to be modelled'\n", "\n", "MS_selector = widgets.ToggleButtons(options=[MS_yes, MS_no],\n", " value=MS_yes,\n", " description='Main Sequence?',\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " button_style='', \n", " tooltips=[MS_yes_descript, MS_no_descript],\n", " layout=widgets.Layout(width=ControlColWidth, \n", " height='70px', \n", " flex_flow='row',\n", " align_items='center',\n", " align_contents='center',\n", " overflow='visible')\n", " )\n", "\n", "\n", "##\n", "##Create controls for stellar parameters \n", "##\n", "\n", "## Mass\n", "\n", "mass1_slider = widgets.FloatSlider(\n", " value=init_mass,\n", " min=min_mass,\n", " max=max_mass+(mass_step/2),\n", " step=mass_step,\n", " description=\"Star 1 mass\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.1f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible')\n", ")\n", "\n", "mass2_slider = widgets.FloatSlider(\n", " value=init_mass,\n", " min=min_mass,\n", " max=max_mass+(mass_step/2),\n", " step=mass_step,\n", " description=\"Star 2 mass\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.1f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible')\n", ")\n", "\n", "# Define text boxes for readout\n", "mass1_readout = widgets.BoundedFloatText(min=mass1_slider.min, max=mass1_slider.max, \n", " value=mass1_slider.value, \n", " layout=widgets.Layout(width=readout_width, \n", " overflow='visible'))\n", "mass2_readout = widgets.BoundedFloatText(min=mass2_slider.min, max=mass2_slider.max, \n", " value=mass2_slider.value, \n", " layout=widgets.Layout(width=readout_width,\n", " overflow='visible'))\n", "\n", "# Link slider and textboxes\n", "widgets.jslink((mass1_readout, 'value'), (mass1_slider, 'value'))\n", "widgets.jslink((mass2_readout, 'value'), (mass2_slider, 'value'))\n", "\n", "# Create the individual controls for stellar masses\n", "solar_mass = widgets.HTML('M')\n", "mass1_cntl = widgets.HBox([mass1_slider, mass1_readout, solar_mass], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "mass2_cntl = widgets.HBox([mass2_slider, mass2_readout, solar_mass], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "\n", "## Radius\n", "\n", "radius1_slider = widgets.FloatSlider(\n", " value=init_radius,\n", " min=min_radius,\n", " max=max_radius+(radius_step/2),\n", " step=radius_step,\n", " description=\"Radius\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.1f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible')\n", ")\n", "\n", "radius2_slider = widgets.FloatSlider(\n", " value=init_radius,\n", " min=min_radius,\n", " max=max_radius+(radius_step/2),\n", " step=radius_step,\n", " description=\"Radius\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.1f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible')\n", ")\n", "\n", "# Define text boxes for readout\n", "radius1_readout = widgets.BoundedFloatText(min=radius1_slider.min, max=radius1_slider.max, \n", " value=radius1_slider.value, \n", " layout=widgets.Layout(width=readout_width, \n", " overflow='visible'))\n", "radius2_readout = widgets.BoundedFloatText(min=radius2_slider.min, max=radius2_slider.max, \n", " value=radius2_slider.value, \n", " layout=widgets.Layout(width=readout_width,\n", " overflow='visible'))\n", "\n", "# Link slider and textboxes\n", "widgets.jslink((radius1_readout, 'value'), (radius1_slider, 'value'))\n", "widgets.jslink((radius2_readout, 'value'), (radius2_slider, 'value'))\n", "\n", "# Create the individual controls for stellar masses\n", "solar_radius = widgets.HTML('R')\n", "radius1_cntl = widgets.HBox([radius1_slider, radius1_readout, solar_radius], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "radius2_cntl = widgets.HBox([radius2_slider, radius2_readout, solar_radius], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "\n", "## Temperature\n", "\n", "temp1_slider = widgets.IntSlider(\n", " value=init_temp,\n", " min=min_temp,\n", " max=max_temp,\n", " step=temp_step,\n", " description=\"Temperature\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.1f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible')\n", ")\n", "\n", "temp2_slider = widgets.IntSlider(\n", " value=init_temp,\n", " min=min_temp,\n", " max=max_temp,\n", " step=temp_step,\n", " description=\"Temperature\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.1f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible')\n", ")\n", "\n", "# Define text boxes for readout\n", "temp1_readout = widgets.BoundedIntText(min=temp1_slider.min, max=temp1_slider.max, \n", " value=temp1_slider.value, \n", " layout=widgets.Layout(width=readout_width, \n", " overflow='visible'))\n", "temp2_readout = widgets.BoundedIntText(min=temp2_slider.min, max=temp2_slider.max, \n", " value=temp2_slider.value, \n", " layout=widgets.Layout(width=readout_width,\n", " overflow='visible'))\n", "\n", "# Link slider and textboxes\n", "widgets.jslink((temp1_readout, 'value'), (temp1_slider, 'value'))\n", "widgets.jslink((temp2_readout, 'value'), (temp2_slider, 'value'))\n", "\n", "# Create the individual controls for stellar masses\n", "Kelvin = widgets.Label('K')\n", "temp1_cntl = widgets.HBox([temp1_slider, temp1_readout, Kelvin], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "temp2_cntl = widgets.HBox([temp2_slider, temp2_readout, Kelvin], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "\n", "##\n", "##Create controls for system properties\n", "##\n", "\n", "# These sliders change entire orbital model and should NOT be continously updated\n", "semimajor_slider = widgets.FloatSlider(value=init_a, \n", " min=min_a, \n", " max=max_a,\n", " step=step_a,\n", " description=\"Semimajor Axis\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.0f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth,\n", " overflow='visible') )\n", "\n", "ecc_slider = widgets.FloatSlider(value=init_ecc,\n", " min=0,\n", " max=0.8,\n", " step=0.02,\n", " description=\"Eccentricity\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.2f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible') )\n", "\n", "phi_slider = widgets.FloatSlider(value=init_phi,\n", " min=0,\n", " max=180,\n", " step=1,\n", " description=\"Major Axis Longitude\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.0f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth,\n", " overflow='visible') )\n", "\n", "incl_slider = widgets.FloatSlider(value=init_incl,\n", " min=0,\n", " max=90,\n", " step=1,\n", " description=\"Inclination\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=False,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.0f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, \n", " overflow='visible') )\n", "\n", "# Define text boxes for readout\n", "semimajor_readout = widgets.BoundedFloatText(min=semimajor_slider.min, max=semimajor_slider.max, \n", " value=semimajor_slider.value, \n", " layout=widgets.Layout(width=readout_width, \n", " overflow='visible'))\n", "ecc_readout = widgets.BoundedFloatText(min=ecc_slider.min, max=ecc_slider.max, \n", " value=ecc_slider.value, \n", " layout=widgets.Layout(width=readout_width, \n", " overflow='visible'))\n", "incl_readout = widgets.BoundedFloatText(min=incl_slider.min, max=incl_slider.max, \n", " value=incl_slider.value, \n", " layout=widgets.Layout(width=readout_width, \n", " overflow='visible'))\n", "phi_readout = widgets.BoundedFloatText(min=phi_slider.min, max=phi_slider.max, \n", " value=phi_slider.value, \n", " layout=widgets.Layout(width=readout_width, \n", " overflow='visible'))\n", "# Link slider and textboxes\n", "widgets.jslink((semimajor_readout, 'value'), (semimajor_slider, 'value'))\n", "widgets.jslink((ecc_readout, 'value'), (ecc_slider, 'value'))\n", "widgets.jslink((incl_readout, 'value'), (incl_slider, 'value'))\n", "widgets.jslink((phi_readout, 'value'), (phi_slider, 'value'))\n", "\n", "# Create the individual controls for system properties\n", "Solar_radius = widgets.HTML('R', layout=widgets.Layout(overflow='visible'))\n", "AU_label = widgets.HTML('AU', layout=widgets.Layout(overflow='visible'))\n", "deg_label = widgets.HTML('°', layout=widgets.Layout(overflow='visible'))\n", "semimajor_cntl = widgets.HBox([semimajor_slider, semimajor_readout, AU_label], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "ecc_cntl = widgets.HBox([ecc_slider, ecc_readout], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "incl_cntl = widgets.HBox([incl_slider, incl_readout, deg_label], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "phi_cntl = widgets.HBox([phi_slider, phi_readout, deg_label], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " overflow='visible'))\n", "\n", "##\n", "## Orbital playback controls\n", "##\n", "phase_slider = widgets.IntSlider(value=0,\n", " min=0,\n", " max=N,\n", " step=1,\n", " description=\"Phase\",\n", " style = {'description_width': 'initial'},\n", " disabled=False,\n", " continuous_update=True,\n", " orientation='horizontal',\n", " readout=False,\n", " readout_format='.0f',\n", " layout=widgets.Layout(width=slider_width, min_width=slider_minwidth,\n", " overflow='visible') )\n", "phase_play = widgets.Play(interval = 1, \n", " value = phase_slider.min, \n", " min=phase_slider.min, \n", " max=phase_slider.max, \n", " step=2, \n", " description=\"Press play\", \n", " disabled=False, \n", " show_repeat=True,\n", " layout=widgets.Layout(overflow='visible'))\n", "widgets.jslink((phase_play, 'value'), (phase_slider, 'value'))\n", "phase_cntl = widgets.HBox([phase_slider, phase_play], \n", " layout=widgets.Layout(width=SimWidth, \n", " overflow='visible'))\n", "\n", "draw_grid = widgets.Checkbox(value=True,\n", " description='Display orbital plane and orbital path',\n", " disabled=False,\n", " layout=widgets.Layout(width=ControlColWidth,\n", " overflow='visible') )\n", "\n", "##\n", "## Create text boxes for reporting certain system parameters\n", "##\n", "P_output = widgets.Text(value = str(0),\n", " description = 'Orbital Period (Days)',\n", " style = {'description_width': 'initial'},\n", " disabled = True, \n", " layout=widgets.Layout(width=inform_width, height='40px',\n", " overflow='visible'))\n", "\n", "ap_output = widgets.Text(value = str(0),\n", " description = 'Periastron (R)',\n", " style = {'description_width': 'initial'},\n", " disabled = True, \n", " layout=widgets.Layout(width=inform_width, \n", " overflow='visible'))\n", "\n", "aa_output = widgets.Text(value = str(0),\n", " description = 'Apastron ($R_\\odot$)',\n", " style = {'description_width': 'initial'},\n", " disabled = True, \n", " layout=widgets.Layout(width=inform_width, \n", " overflow='visible'))\n", "\n", "gridsep_output = widgets.Text(value = str(0),\n", " description = 'Grid Spacing ($R_\\odot$)',\n", " style = {'description_width': 'initial'},\n", " disabled = True, \n", " layout=widgets.Layout(width=inform_width, \n", " overflow='visible'))\n", "\n", "luminosity = widgets.HTML('Luminosity (L)  ')\n", "\n", "L1_output = widgets.Text(value = str(0),\n", " description = 'Star 1: ',\n", " style = {'description_width': 'initial'},\n", " disabled = True, \n", " layout=widgets.Layout(width=lum_width, \n", " overflow='visible'))\n", "\n", "L2_output = widgets.Text(value = str(0),\n", " description = 'Star 2: ',\n", " style = {'description_width': 'initial'},\n", " disabled = True, \n", " layout=widgets.Layout(width=lum_width, \n", " overflow='visible'))\n", "\n", "lum_info = widgets.HBox([luminosity, L1_output, L2_output],\n", " layout=widgets.Layout(height='40px', overflow='visible'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize orbit control sliders\n", "semimajor_slider.value = init_a\n", "ecc_slider.value = init_ecc\n", "incl_slider.value = init_incl\n", "phi_slider.value = init_phi\n", "\n", "# Initialize stellar mass sliders\n", "mass1_slider.value = init_mass\n", "mass2_slider.value = init_mass\n", "\n", "# Set initial parameters based on stellar mass assuming main sequence stars\n", "(radius1_slider.value, temp1_slider.value, hexcolor1, \n", " radius2_slider.value, temp2_slider.value, hexcolor2) = ConfigBothStars(mass1_slider.value, mass2_slider.value)\n", "\n", "# Set luminosities\n", "L1_output.value = str(nf.SigFig((temp1_slider.value/star.Te_Sun)**4 * radius1_slider.value**2, 3))\n", "L2_output.value = str(nf.SigFig((temp2_slider.value/star.Te_Sun)**4 * radius2_slider.value**2, 3))\n", "\n", "# Initialize time index\n", "t_idx = 0\n", "\n", "# Build the initial Binary Star Model with radial velocity and light curves turned off\n", "# and semimajor axis in solar radii\n", "bsm = star.BinaryStarModel(mass1=mass1_slider.value, \n", " mass2=mass2_slider.value,\n", " rad1=radius1_slider.value,\n", " rad2=radius2_slider.value,\n", " temp1=temp1_slider.value,\n", " temp2=temp2_slider.value,\n", " a=semimajor_slider.value,\n", " e=ecc_slider.value,\n", " phi=phi_slider.value,\n", " N=N, \n", " Na=Na, \n", " Ntheta=Ntheta, \n", " rv_init=False, \n", " lc_init=False,\n", " a_in_AU=True)\n", "\n", "# Convert units\n", "P_output.value = \"{0:.1f}\".format(bsm.P)\n", "ap_output.value = \"{0:.1f}\".format(bsm.ap*AU2RSun)\n", "aa_output.value = \"{0:.1f}\".format(bsm.aa*AU2RSun)\n", "\n", "##\n", "## Set Up 3D Simulation and controls for left side\n", "##\n", "\n", "# creates the object that gets displayed to the screen (defaulting to showing orbital paths)\n", "binary_view = star.BinaryStarViewer(bsm=bsm, t_idx0=t_idx, view_width=view_width, \n", " view_height=view_height, \n", " draw_grid=draw_grid.value,\n", " draw_orbits=draw_grid.value)\n", "binary_renderer = binary_view.renderer\n", "\n", "#\n", "# Construct left half controls\n", "#\n", "\n", "# Spacer widget\n", "spacer = widgets.HTML('

', layout=widgets.Layout(width='10px', overflow='visible'))\n", "\n", "# Create play button to control phase value automatically\n", "phase_title_default = 'Controls for Orbital Motion:'\n", "phase_title_collision = 'COLLISION DETECTED! CONTROLS DISABLED!'\n", "phase_title = widgets.HTML(phase_title_default)\n", "phase_controls = widgets.VBox([phase_title, phase_cntl], \n", " layout=widgets.Layout(width=SimWidth, \n", " overflow='visible'))\n", "\n", "# Creates System Parameter Controls (e.g. orbital property controls)\n", "sys_title_default = 'System Parameters:'\n", "sys_title_scaling = 'System Parameters (Stars\\' sizes are NOT TO SCALE!):'\n", "if (binary_view.multiplier > 1):\n", " sys_title = widgets.HTML(value=sys_title_scaling, layout=widgets.Layout(overflow='visible'))\n", "else:\n", " sys_title = widgets.HTML(value=sys_title_default, layout=widgets.Layout(overflow='visible'))\n", "starorbit_controls = widgets.VBox([sys_title, \n", " semimajor_cntl,\n", " widgets.HBox([spacer, widgets.VBox([P_output])]),\n", " ecc_cntl, \n", " incl_cntl, \n", " phi_cntl, \n", " spacer],\n", " layout=widgets.Layout(overflow='visible'))\n", "\n", "# Assemble items in left column\n", "sim_view = widgets.HBox([widgets.HTML('

Model View

'), binary_renderer], \n", " layout=widgets.Layout(width=ControlColWidth, \n", " flex_flow='column',\n", " align_items='center',\n", " align_contents='center',\n", " overflow='visible'))\n", "left_column = widgets.VBox([sim_view, draw_grid, phase_controls, starorbit_controls], \n", " layout=widgets.Layout(width=ControlColWidth,\n", " overflow='visible'))\n", "\n", "\n", "##\n", "## Build Controls on right side\n", "##\n", "\n", "# Compute light curve or radial velocity curve and select which to display initially\n", "graph_update()\n", "\n", "# Creates Stellar Mass Controls\n", "star_title = widgets.HTML('Stellar Properties:', layout=widgets.Layout(overflow='hidden'))\n", "mass1_title = widgets.HTML(value=\"Star 1 Parameters\", layout=widgets.Layout(overflow='hidden'))\n", "mass2_title = widgets.HTML(value=\"Star 2 Parameters\", layout=widgets.Layout(overflow='hidden'))\n", "star_controls = widgets.VBox([star_title, MS_selector, \n", " mass1_title, mass1_cntl, radius1_cntl, temp1_cntl, \n", " mass2_title, mass2_cntl, radius2_cntl, temp2_cntl, \n", " widgets.HBox([spacer, lum_info])],\n", " layout=widgets.Layout(overflow='visible'))\n", "\n", "# Decide which controls to initially display depending on Main Sequence setting\n", "if (MS_selector.value == MS_yes):\n", " radius1_slider.disabled = True\n", " radius2_slider.disabled = True\n", " temp1_slider.disabled = True\n", " temp2_slider.disabled = True\n", " radius1_readout.disabled = True\n", " radius2_readout.disabled = True\n", " temp1_readout.disabled = True\n", " temp2_readout.disabled = True \n", "else:\n", " radius1_slider.disabled = False\n", " radius2_slider.disabled = False\n", " temp1_slider.disabled = False\n", " temp2_slider.disabled = False\n", " radius1_readout.disabled = False\n", " radius2_readout.disabled = False\n", " temp1_readout.disabled = False\n", " temp2_readout.disabled = False\n", " \n", "# Creates a vertical box for the right control panel\n", "graph_box = widgets.VBox([fig_selector, graph_fig], \n", " layout=widgets.Layout(width=ControlColWidth,\n", " overflow='visible') )\n", "right_column = widgets.VBox([graph_box, star_controls], \n", " layout=widgets.Layout(width=ControlColWidth,\n", " overflow='visible') )\n", "\n", "# Places the figure, sliders, and output into a Vbox. The figure is \n", "# alone in the top, while the sliders and output are in a Hbox\n", "# inside the bottom of the Vbox.\n", "MainDisplay = widgets.HBox([left_column, spacer, right_column])\n", "\n", "# Sets the dimensions of the box. Sets the entire width and the height of \n", "# just the top.\n", "MainDisplay.layout.width = EntireWidth\n", "MainDisplay.layout.overflow = 'hidden'\n", "display(MainDisplay)\n", "\n", "##\n", "## Turn on interactivity by linking sliders to functions changing the simulation.\n", "##\n", "\n", "# This control determines which graph to display\n", "fig_selector.observe(graph_update, names=['value'])\n", "\n", "# This control determines if we eassume main sequence or not\n", "MS_selector.observe(MS_update, names=['value'])\n", "\n", "# If mass changes, we also need to check for radius and temperature changes\n", "mass1_link = traitlets.directional_link((mass1_slider, 'value'), (bsm, 'mass1'))\n", "mass2_link = traitlets.directional_link((mass2_slider, 'value'), (bsm, 'mass2'))\n", "\n", "# These sliders adjust stellar properties and make calls to binary star model and viewer to automatically\n", "# adjust model (if needed) and view\n", "radius1_link = traitlets.directional_link((radius1_slider, 'value'), (bsm, 'rad1'))\n", "radius2_link = traitlets.directional_link((radius2_slider, 'value'), (bsm, 'rad2'))\n", "temp1_link = traitlets.directional_link((temp1_slider, 'value'), (bsm, 'temp1'))\n", "temp2_link = traitlets.directional_link((temp2_slider, 'value'), (bsm, 'temp2'))\n", "\n", "# Handle changes in orbit directly in binary star model object\n", "a_link = traitlets.directional_link((semimajor_slider, 'value'), (bsm, 'a'))\n", "ecc_link = traitlets.directional_link((ecc_slider, 'value'), (bsm, 'e'))\n", "phi_link = traitlets.directional_link((phi_slider, 'value'), (bsm, 'phi'))\n", "\n", "# Call property_update if anything that potentially changes the orbit or luminosity is changed\n", "mass1_slider.observe(property_update, names=['value'])\n", "mass2_slider.observe(property_update, names=['value'])\n", "radius1_slider.observe(property_update, names=['value'])\n", "radius2_slider.observe(property_update, names=['value'])\n", "temp1_slider.observe(property_update, names=['value'])\n", "temp2_slider.observe(property_update, names=['value'])\n", "semimajor_slider.observe(property_update, names=['value'])\n", "ecc_slider.observe(property_update, names=['value'])\n", "phi_slider.observe(property_update, names=['value'])\n", "\n", "# Handle changes in inclination directly in binary star model and binary star viewer\n", "incl_link = traitlets.directional_link((incl_slider, 'value'), (bsm, 'incl'))\n", "incl_viewlink = traitlets.directional_link((incl_slider, 'value'), (binary_view, 'incl'))\n", "incl_slider.observe(inclination_update, names=['value'])\n", "\n", "# This slider just changes what phase of the orbit to display\n", "phase_link = traitlets.directional_link((phase_slider, 'value'), (binary_view, 't_idx'))\n", "phase_slider.observe(position_update, names=['value'])\n", "\n", "# if Binary Star Model orbit model changes, trigger the Binary Star Viewer to change\n", "orbit_change_link = traitlets.directional_link((bsm, 'mdl_counter'), (binary_view, 'mdl_counter'))\n", "orbit_change_link = traitlets.directional_link((draw_grid, 'value'), (binary_view, 'draw_grid'))\n", "orbit_change_link = traitlets.directional_link((draw_grid, 'value'), (binary_view, 'draw_orbits'))\n", "draw_grid.observe(scene_update, names=['value'])\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.0" } }, "nbformat": 4, "nbformat_minor": 2 }