<jupyter_start><jupyter_text>Exoplanet System Simulation InteractiveThis interactive application simulates the orbit of of a single exoplanet around a star, taking into account- the masses of the planet and the star, and- the shape of and orientation of the orbit in spaceto accurately model the orbit as well as the observable radial velocites and light curve of the exoplanetary system as observed from Earth.  This interactively crudely makes two estimates:- The temperature of the planet is estimated by assuming it reflect light like Earth does and that its temperature doesn't change during its orbit (so it uses the average distance from the star for temperature calculations). - The radius of the planet is estimated by fitting the 636 exoplanets with both estimated masses and radii (obtained from the NASA Exoplanet Archive on 15 August 2018).  It assumes no physics, and so is probably a very rough estimate.**Note**: This code takes an approach originally seen in the *TwoStars* code by <NAME> Ostlie.  That code was used for modelling binary stars and has been extensively changed to handle exoplanets and optimized for the Python programming language. Model versus Reality A reminder that the model shown on the left hand side of the exoplanet orbiting the star is supposed to be just that, a model.  Remember we rarely actually visually see the exoplanets in system like this as they are in reality billions of times less luminous than the star.  What we actually observe is a radial velocity or light curve, this model is an explanation for those observations.<jupyter_code># Developed by <NAME> starting June 19, 2018 and proceeding through the Summer of 2018
#
# This simulation is a modified version of the Binary Star Simulation Interactive reworked 
# to handle exoplanetary systems.
from IPython.display import display, HTML
import numpy as np
import ipywidgets as widgets
import traitlets
import pythreejs as p3j
import bqplot as bq
import tempNcolor as tc
import starlib as star
import number_formatting as nf
## FUNCTIONS ##

def property_update(change=None):
    '''
    This function updates the stellar properties (and orbital properties) and is meant to be used
    when there are changes to stellar properties as controled by various ipywidgets on screen.
    '''
    global bsm

    # Turn off continuous updating to allow quick changing of several variables
    # NOTE: Masses automatically update in the binary star model.
    bsm.continuous_update = False  
    (starradius.value, startemp.value, hexcolor1) = star.ConfigStar(starmass_slider.value)
    (exoradius.value, exotemp.value, hexcolor2) = ConfigPlanet(exomass_slider.value, 
                                                                      semimajor_slider.value*AU2RSun, 
                                                                      startemp.value, 
                                                                      starradius.value)
    # Copy radius and temperature info to binary star model
    bsm.rad1 = starradius.value
    bsm.rad2 = exoradius_in_RSun.value
    bsm.temp1 = startemp.value
    bsm.temp2 = exotemp.value
    
    # Turn on continuous updating again
    bsm.continuous_update = True 

    # Force a binary star model update (which should update view as well)
    bsm.force_update()
    
    # If there is a collision, stop the phase changes, otherwise allow access to changing phase
    if (bsm.collision):
        phase_title.value = phase_title_collision
        phase_play.step = 0
        phase_play.disabled = True
        phase_slider.disabled = True
    else:
        # Make sure orbital phase can be adjusted
        phase_title.value = phase_title_default
        phase_play.step = 1
        phase_play.disabled = False
        phase_slider.disabled = False

    # Set luminosities
    L1_output.value = str(nf.SigFig((startemp.value/star.Te_Sun)**4 * starradius.value**2, 3))
    L2_output.value = str(nf.SigFig((exotemp.value/star.Te_Sun)**4 * exoradius.value**2, 3))
    
    # Update the on screen label to indicate unreal scaling if necessary
    if (exosystem_view.multiplier > 1):
        sys_title.value = sys_title_scaling
    else:
        sys_title.value=sys_title_default
        
    # Revise orbital readouts
    P_output.value = "{0:.1f}".format(bsm.P)
    ap_output.value = "{0:.1f}".format(bsm.ap*AU2RSun)
    aa_output.value = "{0:.1f}".format(bsm.aa*AU2RSun)
    
    # Perform the inclination update to force an update of the display
    inclination_update()

    
def inclination_update(change=None):
    '''
    This function updates the star system's inclination to the plane of the sky and updates the graph shown
    '''
    global bsm
    
    # Retrieve updated radial velocity or light curves and then update figure
    # (in OO version of this code, the bsm should have updated automatically following the inclination angle change)
    if (fig_selector.value == rv_val):
        update_radvel_curve(bsm.radvel_info, bsm.orbit_info)
    else:
        update_light_curve(bsm.lc_info)
    
    
def position_update(change=None):
    '''
    This function updates the two stars' phase line on whatever graph is shown.  The viewer changes are handled
    automatically by linking the slider to the t_idx value in the exosystem_view object.
    '''
    # Update the phase line in radial velocity or light curve
    if (fig_selector.value == rv_val):
        rv_phase_line.x = [bsm.radvel_info['phase'][phase_slider.value], bsm.radvel_info['phase'][phase_slider.value]]
    else:
        lc_phase_line.x = [bsm.lc_info['phase'][phase_slider.value], bsm.lc_info['phase'][phase_slider.value]]

        
def scene_update(change=None):
    '''
    This function updates the notificaiton of the scaling of the objects and the grid/orbit drawing if necessary.
    '''
    if (exosystem_view.multiplier > 1):
        sys_title.value = sys_title_scaling
    else:
        sys_title.value=sys_title_default
    
    # Reset the grid and orbit
    exosystem_view.reset_grid_and_orbit()

def zoom_update(change=None):
    '''
    This function handles updates to the level of magnification.
    '''
    exosystem_view._starcam.zoom=zoom_slider.value
        
def graph_update(change=None):
    '''
    This function switches which graph to plot
    '''
    global bsm, graph_fig
    
    # Check if graph exists, if it doesn't, create it.
    try:
        graph_fig
    except NameError:
        graph_fig = None
    
    if (fig_selector.value == rv_val):
        bsm.wipe_lc_info()
        bsm.set_radvel_info()
        new_fig = create_radvel_curve(bsm.radvel_info, bsm.orbit_info)
    else:
        bsm.wipe_radvel_info()
        bsm.set_lc_info()
        new_fig = create_light_curve(bsm.lc_info)
    
    if (graph_fig is None):
        graph_fig = new_fig
    else:
        graph_fig.marks = new_fig.marks
        graph_fig.axes = new_fig.axes
        graph_fig.title = new_fig.title
        graph_fig.layout = new_fig.layout


def create_light_curve(light_curve):
    '''
    Initialize the entire light curve 
    '''
    global lc_line, lc_phase_line
    
    # Determine deepest possible eclipse (90 degree inclination)
    edgeon_lc = star.LightCurveInfo(bsm.orbit_info, 90, bsm.rad1, bsm.rad2, bsm.temp1, bsm.temp2)
    min_flux = np.floor(np.min(edgeon_lc['F_norm'])*100)/100   # Round minimum to nearest percent
    
    # Set scales
    sc_x = bq.LinearScale(min=0, max=1)
    sc_y = bq.LinearScale(min=min_flux, max=1)

    # Build the light curve
    lc_line = bq.Lines(x=light_curve['phase'], y=light_curve['F_norm'], scales={'x': sc_x, 'y': sc_y}, 
                      colors=['Black'])
    
    # Indicate the current phase
    x_phase = [light_curve['phase'][phase_slider.value], light_curve['phase'][phase_slider.value]]
    y_phase = [min_flux, 1] 
    lc_phase_line = bq.Lines(x=x_phase, y=y_phase, scales={'x': sc_x, 'y': sc_y}, 
                      colors=['Red'])
    
    # Setup axes and return figure
    ax_x = bq.Axis(scale=sc_x, label='Phase')
    ax_y = bq.Axis(scale=sc_y, orientation='vertical', label='Fraction of Maximum Flux')
    return bq.Figure(marks=[lc_line, lc_phase_line], axes=[ax_x, ax_y], title='Light Curve',
                     layout=widgets.Layout(width=graph_width, height=graph_height, margin='5px 5px 5px 5px'))


def update_light_curve(lc_info):
    '''
    Update the light curve in event of inclination change
    '''
    global lc_line
    lc_line.x=lc_info['phase']
    lc_line.y=lc_info['F_norm']

    
def create_radvel_curve(radvel_info, orbit_info):
    '''
    Initialize the radial velocity curve
    '''
    global star_line, rv_phase_line
    
    # Set up the scales
    sc_x = bq.LinearScale()
    sc_y = bq.LinearScale()

    # Indicate the current phase
    x_phase = [radvel_info['phase'][phase_slider.value], radvel_info['phase'][phase_slider.value]]
    
    # Scale to min/max velocity at all inclinations (assume no systemic radial velocity)
    maxval = -np.min(orbit_info['vx1']*1000)  # use m/s
    minval = -np.max(orbit_info['vx1']*1000)  # use m/s
    y_phase = [minval, maxval] 
    rv_phase_line = bq.Lines(x=x_phase, y=y_phase, scales={'x': sc_x, 'y': sc_y}, 
                      colors=['Red'])
    
    # Draw the radial velocity curves (in m/s)
    star_line = bq.Lines(x=radvel_info['phase'], y=radvel_info['v1r']*1000, scales={'x': sc_x, 'y': sc_y},
                         colors=['DarkOrange'], labels=['Star'], display_legend=True)
    
    # Setup axes and return (initially invisible) figure
    ax_x = bq.Axis(scale=sc_x, label='Phase')
    ax_y = bq.Axis(scale=sc_y, orientation='vertical', label='Radial velocity (m/s)')
    ax_y.label_offset = '3.5em'
    return bq.Figure(marks=[star_line, rv_phase_line], axes=[ax_x, ax_y], title='Radial Velocity Curve',
                     layout=widgets.Layout(width=graph_width, height=graph_height, margin='5px 5px 5px 5px'))


def update_radvel_curve(radvel_info, orbit_info):
    '''
    Update the radial velocity curve (only stellar curve would be visible)
    '''
    global star_line, rv_phase_line
    
    star_line.x=radvel_info['phase']
    star_line.y=radvel_info['v1r']*1000   # use m/s
    
    # Rescale phase line limits if orbit changes
    maxval = -np.min(orbit_info['vx1']*1000)  # use m/s
    minval = -np.max(orbit_info['vx1']*1000)  # use m/s
    rv_phase_line.y = [minval, maxval] 


def ConfigPlanet(M_exo, semimajor, T_star, R_star, albedo = 0.3):
    """
    Configure planet parameters.
    
    Requires R_star and semimajor be in the SAME units.
    Requires M_exo be in Jupiter masses.
    
    Radius is based on a fit of exoplanet mass to radius, it is very crude.
    
    For temperature we use a crude non-greenhouse model that assumes 
    average distance from the star (semimajor axis) sets the temperature.
    """
    # Estimate the radius of the planet in Jupiter Radii given mass in Jupiter Masses
    # Limit the masses to range from 0.01 to 20 Jupiter masses to stay within fit limits.
    MvsR_coeff = [0.003216146262957223, 0.02517307913211346, 0.042740981066343345,
                  -0.10205588511119912, -0.29830452706869, 0.3442309265319224, 
                  0.04882412681603183]
    logM_vs_logR_fit = np.poly1d(MvsR_coeff)
    logR_est = logM_vs_logR_fit(np.log10(M_exo))
    R_est = pow(10, logR_est)
    
    # Set color of planet to be 'cool star' like
    planet_colortemp = 4000
    hexcolor = tc.rgb2hex(tc.temp2rgb(planet_colortemp))[0]
    
    # Compute temperature
    # Works as long as R_star and semimajor axis are in the same units
    T_exo = pow((1 - albedo), 0.25)*pow(R_star/(2*semimajor), 0.5)*T_star
    
    return (R_est, T_exo, hexcolor)
## INTERACTIVE/DISPLAY WIDGETS ##

# Conversion constants
R_Jupiter = 6.9911e7  # Radius of Jupiter in km
M_Jupiter = 1.8982e27   # Mass of Jupiter in kg
RJup_in_RSun = R_Jupiter/star.R_Sun
MJup_in_MSun = M_Jupiter/star.M_Sun
RSun2AU = star.R_Sun/star.AU
AU2RSun = 1/RSun2AU

# Define constants
min_mass = 0.1   # Minimum stellar mass in solar masses
max_mass = 5    # Maximum stellar mass in solar masses
mass_step = 0.1  # Step size for stellar mass slider in solar masses
init_mass = 1    # Initial mass of star in solar masses

min_exomass = 0.1   # Minimum planetary mass in Jupiter masses
max_exomass = 5    # Maximum planetary mass in Jupiter masses
exomass_step = 0.1  # Step size for mass sliders in Jupiter masses
init_exomass = 1    # Initial mass of planet in Jupiter masses

min_radius = 0.2   # Minimum stellar radius in solar radii
max_radius = 100   # Maximum stellar radius in solar radii
radius_step = 0.1  # Step size for stellar radius slider in solar radii
init_radius = 1    # Initial radius of star in solar radii

min_exoradius = 0.05   # Minimum planetary radius in Jupiter radii
max_exoradius = 20   # Maximum planetary radius in Jupiter radii
exoradius_step = 0.05  # Step size for planetary radius slider in Jupiter radii
init_exoradius = 1    # Initial radius of planet in Jupiter radii

min_temp = 3100    # Minimum stellar temperature in K
max_temp = 40000   # Maximum stellar temperature in K
temp_step = 10     # Step size for temperature slider in K
init_temp = int(star.Te_Sun/10)*10 # Initial temperature of star in K

min_a = 0.02      # Minimum semimajor axis of system in AU
max_a = 5         # Maximum semimajor axis of system in AU
step_a = 0.02     # Step size for separation slider in AU
init_a = 0.3      # Start off with the two objects this close together

init_incl = 0.0   # Initial inclination value (non-zero value avoids odd orientation of FOV at start)
init_phi = 0      # Initial semimajor axis phase angle
init_ecc = 0.2    # Initial orbital eccentricity

view_factor = 1.25  # How many times the maximum distance to place the viewer
N = 3000          # Number of time steps to use for orbit
Na = 100          # Number of annuli to break up stars into for computing eclipse fraction
Ntheta = 360      # Number of angular steps to break up stars into for computing eclipse fraction

#
# Define some widths to use throughout for layout of controls
#

# Set simulation size
view_width = 350
view_height = view_width

# Initialize slider sizes
EntireWidth = '950px'
SimWidth = '{0:.0f}px'.format(view_width)
ControlColWidth = '450px'
slider_width = '300px'
slider_minwidth = '250px'
readout_width = '70px'
lum_width = '120px'
inform_width = '200px'
graph_width = '475px'
graph_height = '300px'

##
## Create control for selecting Figure
##
rv_val = 'Radial Velocity Curve'
lc_val = 'Light Curve'
fig_selector = widgets.ToggleButtons(options=[rv_val, lc_val],
                                    value=rv_val,
                                    #description='Plot to Display:',
                                    #style = {'description_width': 'initial'},
                                    disabled=False,
                                    orientation='horizontal',
                                    layout=widgets.Layout(width=ControlColWidth,
                                                          height='20px', 
                                                          overflow_x='visible', 
                                                          overflow_y='visible')
                                   )



##
##Create controls for stellar parameters 
##

## Mass

starmass_slider = widgets.FloatSlider(
    value=init_mass,
    min=min_mass,
    max=max_mass+(mass_step/2),
    step=mass_step,
    description="Star mass",
    style = {'description_width': 'initial'},
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=False,
    readout_format='.1f',
    layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, 
                          overflow_x='visible', overflow_y='visible')
)

exomass_slider = widgets.FloatSlider(
    value=init_mass,
    min=min_exomass,
    max=max_exomass+(exomass_step/2),
    step=exomass_step,
    description="Planet mass",
    style = {'description_width': 'initial'},
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=False,
    readout_format='.1f',
    layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, 
                          overflow_x='visible', overflow_y='visible')
)

# Define text boxes for readout
starmass_readout = widgets.BoundedFloatText(min=starmass_slider.min, max=starmass_slider.max, 
                                         value=starmass_slider.value, 
                                         layout=widgets.Layout(width=readout_width, 
                                                               overflow_x='visible', 
                                                               overflow_y='visible'))
exomass_readout = widgets.BoundedFloatText(min=exomass_slider.min, max=exomass_slider.max, 
                                         value=exomass_slider.value, 
                                         layout=widgets.Layout(width=readout_width,
                                                               overflow_x='visible', 
                                                               overflow_y='visible'))

# Link slider and textboxes
widgets.jslink((starmass_readout, 'value'), (starmass_slider, 'value'))
widgets.jslink((exomass_readout, 'value'), (exomass_slider, 'value'))

# Create the individual controls for stellar masses
solar_mass = widgets.HTML('M<sub>&#x2609;</sub>')
jupiter_mass = widgets.HTML('M<sub>Jup</sub>')
starmass_cntl = widgets.HBox([starmass_slider, starmass_readout, solar_mass], 
                          layout=widgets.Layout(width=ControlColWidth, 
                                                overflow_x='visible', 
                                                overflow_y='visible'))
exomass_cntl = widgets.HBox([exomass_slider, exomass_readout, jupiter_mass], 
                          layout=widgets.Layout(width=ControlColWidth, 
                                                overflow_x='visible', 
                                                overflow_y='visible'))

# Define planetary parameters in solar units as globals
exomass_in_MSun = widgets.FloatText(value=exomass_slider.value*MJup_in_MSun)
def ConvertMass(change):
    global exomass_in_MSun
    exomass_in_MSun.value = exomass_slider.value*MJup_in_MSun

# Automatically update the values in solar units when values for planet change
exomass_slider.observe(ConvertMass, names=['value'])


## Radius

# Define text boxes for readout
starradius = widgets.FloatText(value=init_radius,
                               disabled=True,
                               layout=widgets.Layout(width=readout_width,
                                                     overflow_x='visible',
                                                     overflow_y='visible'))
exoradius = widgets.FloatText(value=init_exoradius,
                              disabled=True,
                              layout=widgets.Layout(width=readout_width,
                                                    overflow_x='visible',
                                                    overflow_y='visible'))

# Create the individual controls for stellar masses
radius_label = widgets.HTML('Radius: ')
solar_radius = widgets.HTML('R<sub>&#x2609;</sub>')
jupiter_radius = widgets.HTML('R<sub>Jup</sub>')

# Define planetary parameters in solar units as globals
exoradius_in_RSun = widgets.FloatText(value=exoradius.value*RJup_in_RSun)

def ConvertRadius(change):
    global exoradius_in_RSun
    exoradius_in_RSun.value = exoradius.value*RJup_in_RSun

# Automatically update the values in solar units when values for planet change
exoradius.observe(ConvertRadius, names=['value'])

## Temperature

startemp = widgets.IntText(value=init_temp,
                           disabled=True,
                           layout=widgets.Layout(width=readout_width,
                                                 overflow_x='visible',
                                                 overflow_y='visible'))
exotemp = widgets.IntText(value=init_temp,
                          disabled=True,
                          layout=widgets.Layout(width=readout_width,
                                                overflow_x='visible',
                                                overflow_y='visible'))

# Create the individual controls for stellar masses
temp_label = widgets.HTML('Temp.: ')
Kelvin = widgets.HTML('K')

## Luminosity
lum_label = widgets.HTML('Lum.:')
solar_lum = widgets.HTML('L<sub>&#x2609;</sub>')

L1_output = widgets.Text(value = str(0),
                         style = {'description_width': 'initial'},
                         disabled = True, 
                         layout=widgets.Layout(width=readout_width, 
                                               overflow_x='visible', overflow_y='visible'))

L2_output = widgets.Text(value = str(0),
                         style = {'description_width': 'initial'},
                         disabled = True, 
                         layout=widgets.Layout(width=readout_width, 
                                               overflow_x='visible', overflow_y='visible'))

## Information strips on star and planet
gap = widgets.HTML('&nbsp;', layout=widgets.Layout(width='10px', overflow='visible'))
star_info = widgets.HBox([radius_label, starradius, solar_radius, gap,
                          temp_label, startemp, Kelvin, gap,
                          lum_label, L1_output, solar_lum],
                          layout=widgets.Layout(width=ControlColWidth, 
                                                overflow_x='visible', 
                                                overflow_y='visible'))
exo_info = widgets.HBox([radius_label, exoradius, jupiter_radius, gap,
                         temp_label, exotemp, Kelvin, gap,
                         lum_label, L2_output, solar_lum],
                        layout=widgets.Layout(width=ControlColWidth,
                                              overflow_x='visible',
                                              overflow_y='visible'))

##
##Create controls for system properties
##

# These sliders change entire orbital model and should NOT be continously updated
semimajor_slider = widgets.FloatSlider(value=init_a, 
                                       min=min_a, 
                                       max=max_a,
                                       step=step_a,
                                       description="Semimajor Axis",
                                       style = {'description_width': 'initial'},
                                       disabled=False,
                                       continuous_update=False,
                                       orientation='horizontal',
                                       readout=False,
                                       readout_format='.0f',
                                       layout=widgets.Layout(width=slider_width, min_width=slider_minwidth,
                                                             overflow_x='visible', overflow_y='visible') )

ecc_slider = widgets.FloatSlider(value=init_ecc,
                                 min=0,
                                 max=0.8,
                                 step=0.02,
                                     description="Eccentricity",
                                 style = {'description_width': 'initial'},
                                 disabled=False,
                                 continuous_update=False,
                                     orientation='horizontal',
                                     readout=False,
                                 readout_format='.2f',
                                 layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, 
                                                       overflow_x='visible', overflow_y='visible') )

phi_slider = widgets.FloatSlider(value=init_phi,
                                 min=0,
                                 max=180,
                                 step=1,
                                 description="Major Axis Longitude",
                                 style = {'description_width': 'initial'},
                                 disabled=False,
                                 continuous_update=False,
                                 orientation='horizontal',
                                     readout=False,
                                 readout_format='.0f',
                                 layout=widgets.Layout(width=slider_width, min_width=slider_minwidth,
                                                       overflow_x='visible', overflow_y='visible') )

incl_slider = widgets.FloatSlider(value=init_incl,
                                  min=0,
                                  max=90,
                                  step=1,
                                  description="Inclination",
                                  style = {'description_width': 'initial'},
                                  disabled=False,
                                  continuous_update=False,
                                  orientation='horizontal',
                                  readout=False,
                                  readout_format='.0f',
                                  layout=widgets.Layout(width=slider_width, min_width=slider_minwidth, 
                                                        overflow_x='visible', overflow_y='visible') )

# Define text boxes for readout
semimajor_readout = widgets.BoundedFloatText(min=semimajor_slider.min, max=semimajor_slider.max, 
                                             value=semimajor_slider.value, 
                                             layout=widgets.Layout(width=readout_width, 
                                                                   overflow_x='visible', 
                                                                   overflow_y='visible'))
ecc_readout = widgets.BoundedFloatText(min=ecc_slider.min, max=ecc_slider.max, 
                                       value=ecc_slider.value, 
                                       layout=widgets.Layout(width=readout_width, 
                                                             overflow_x='visible', 
                                                             overflow_y='visible'))
incl_readout = widgets.BoundedFloatText(min=incl_slider.min, max=incl_slider.max, 
                                        value=incl_slider.value, 
                                        layout=widgets.Layout(width=readout_width, 
                                                              overflow_x='visible', 
                                                              overflow_y='visible'))
phi_readout = widgets.BoundedFloatText(min=phi_slider.min, max=phi_slider.max, 
                                       value=phi_slider.value, 
                                       layout=widgets.Layout(width=readout_width, 
                                                             overflow_x='visible', 
                                                             overflow_y='visible'))
# Link slider and textboxes
widgets.jslink((semimajor_readout, 'value'), (semimajor_slider, 'value'))
widgets.jslink((ecc_readout, 'value'), (ecc_slider, 'value'))
widgets.jslink((incl_readout, 'value'), (incl_slider, 'value'))
widgets.jslink((phi_readout, 'value'), (phi_slider, 'value'))

# Create the individual controls for system properties
Solar_radius = widgets.HTML('R<sub>&#x2609;</sub>', layout=widgets.Layout(overflow_x='visible', 
                                                        overflow_y='visible'))
AU_label = widgets.HTML('AU', layout=widgets.Layout(overflow_x='visible', 
                                                        overflow_y='visible'))
deg_label = widgets.HTML('&deg;', layout=widgets.Layout(overflow_x='visible', 
                                                        overflow_y='visible'))
semimajor_cntl = widgets.HBox([semimajor_slider, semimajor_readout, AU_label], 
                              layout=widgets.Layout(width=ControlColWidth, 
                                                    overflow_x='visible', 
                                                    overflow_y='visible'))
ecc_cntl = widgets.HBox([ecc_slider, ecc_readout], 
                        layout=widgets.Layout(width=ControlColWidth, 
                                              overflow_x='visible', 
                                              overflow_y='visible'))
incl_cntl = widgets.HBox([incl_slider, incl_readout, deg_label], 
                         layout=widgets.Layout(width=ControlColWidth, 
                                               overflow_x='visible', 
                                               overflow_y='visible'))
phi_cntl = widgets.HBox([phi_slider, phi_readout, deg_label], 
                        layout=widgets.Layout(width=ControlColWidth, 
                                              overflow_x='visible', 
                                              overflow_y='visible'))

##
## Orbital playback controls
##
phase_slider = widgets.IntSlider(value=0,
                                   min=0,
                                   max=N,
                                   step=1,
                                   description="Phase",
                                   style = {'description_width': 'initial'},
                                   disabled=False,
                                   continuous_update=True,
                                   orientation='horizontal',
                                   readout=False,
                                   readout_format='.0f',
                                   layout=widgets.Layout(width=slider_width, min_width=slider_minwidth,
                                                         overflow_x='visible', overflow_y='visible') )
phase_play = widgets.Play(interval = 1, 
                          value = phase_slider.min, 
                          min=phase_slider.min, 
                          max=phase_slider.max, 
                          step=2, 
                          description="Press play", 
                          disabled=False, 
                          _repeat=True, show_repeat=False,
                          layout=widgets.Layout(overflow='visible'))
widgets.jslink((phase_play, 'value'), (phase_slider, 'value'))
phase_cntl = widgets.HBox([phase_slider, phase_play], 
                          layout=widgets.Layout(width=ControlColWidth, 
                                                overflow_x='visible', overflow_y='visible'))
##
## Create a viewer controls for scale, zoom, and grid drawing
##

zoom_slider = widgets.FloatSlider(value=1,
                                  min=1,
                                  max=300,
                                  step=0.1,
                                  description="View Magnification Factor",
                                  style = {'description_width': 'initial'},
                                  disabled=False,
                                  continuous_update=True,
                                  orientation='horizontal',
                                  readout=True,
                                  readout_format='.1f',
                                  layout=widgets.Layout(width=ControlColWidth, min_width=ControlColWidth, 
                                                        overflow_x='visible', overflow_y='visible') )

scale_force = widgets.Checkbox(value=False,
                               description='Force objects\' sizes to correct scale with orbit',
                               disabled=False,
                               layout=widgets.Layout(width=ControlColWidth,
                                                     overflow_x='visible', 
                                                     overflow_y='visible') )
draw_grid = widgets.Checkbox(value=True,
                               description='Draw grid and orbit',
                               disabled=False,
                               layout=widgets.Layout(width=ControlColWidth,
                                                     overflow_x='visible', 
                                                     overflow_y='visible') )

##
## Create text boxes for reporting certain system parameters
##
P_output = widgets.Text(value = str(0),
                        description = 'Orbital Period (Days)',
                        style = {'description_width': 'initial'},
                        disabled = True, 
                        layout=widgets.Layout(width=inform_width, height='40px',
                                              overflow_x='visible', overflow_y='visible'))

ap_output = widgets.Text(value = str(0),
                         description = 'Periastron (R<sub>&#x2609;</sub>)',
                         style = {'description_width': 'initial'},
                         disabled = True, 
                         layout=widgets.Layout(width=inform_width, 
                                               overflow_x='visible', overflow_y='visible'))

aa_output = widgets.Text(value = str(0),
                         description = 'Apastron ($R_\odot$)',
                         style = {'description_width': 'initial'},
                         disabled = True, 
                         layout=widgets.Layout(width=inform_width, 
                                               overflow_x='visible', overflow_y='visible'))

gridsep_output = widgets.Text(value = str(0),
                              description = 'Grid Spacing ($R_\odot$)',
                              style = {'description_width': 'initial'},
                              disabled = True, 
                              layout=widgets.Layout(width=inform_width, 
                                                    overflow_x='visible', overflow_y='visible'))
# Initialize orbit control sliders
semimajor_slider.value = init_a
ecc_slider.value = init_ecc
incl_slider.value = init_incl
phi_slider.value = init_phi

# Initialize stellar mass sliders
starmass_slider.value = init_mass
exomass_slider.value = init_mass

# Set initial parameters based on stellar mass assuming main sequence stars
(starradius.value, startemp.value, hexcolor1) = star.ConfigStar(starmass_slider.value)
(exoradius.value, exotemp.value, hexcolor2) = ConfigPlanet(exomass_slider.value,
                                                           semimajor_slider.value*AU2RSun,
                                                           startemp.value,
                                                           starradius.value)
    
# Set luminosities
L1_output.value = str(nf.SigFig((startemp.value/star.Te_Sun)**4 * starradius.value**2, 3))
L2_output.value = str(nf.SigFig((exotemp.value/star.Te_Sun)**4 * exoradius.value**2, 3))

# Initialize time index
t_idx = 0

# Build the initial Binary Star Model with radial velocity and light curves turned off
# and semimajor axis in AU
bsm = star.BinaryStarModel(mass1=starmass_slider.value, 
                           mass2=exomass_in_MSun.value,
                           rad1=starradius.value,
                           rad2=exoradius_in_RSun.value,
                           temp1=startemp.value,
                           temp2=exotemp.value,
                           a=semimajor_slider.value,
                           e=ecc_slider.value,
                           phi=phi_slider.value,
                           N=N, 
                           Na=Na, 
                           Ntheta=Ntheta, 
                           rv_init=False, 
                           lc_init=False,
                           a_in_AU=True)

# Convert units
P_output.value = "{0:.1f}".format(bsm.P)
ap_output.value = "{0:.1f}".format(bsm.ap*AU2RSun)
aa_output.value = "{0:.1f}".format(bsm.aa*AU2RSun)

##
## Set Up 3D Simulation and controls for left side
##

# creates the object that gets displayed to the screen (defaulting to showing orbital paths)
exosystem_view = star.BinaryStarViewer(bsm=bsm, 
                                       t_idx0=t_idx,
                                       lock_scale=scale_force.value,
                                       enable_zoom=False,
                                       draw_grid=draw_grid.value,
                                       draw_orbits=draw_grid.value,
                                       view_width=view_width, 
                                       view_height=view_height)
exosystem_renderer = exosystem_view.renderer

#
# Construct left half controls
#

# Spacer widget
spacer = widgets.HTML('<p>', layout=widgets.Layout(width='10px', overflow='visible'))

# Create play button to control phase value automatically
phase_title_default = '<b>Controls for Orbital Motion</b>:'
phase_title_collision = '<B style="color:red">COLLISION DETECTED! CONTROLS DISABLED!</B>'
phase_title = widgets.HTML(phase_title_default)
phase_controls = widgets.VBox([phase_title, phase_cntl], 
                              layout=widgets.Layout(width=SimWidth, 
                                                   overflow_x='visible',
                                                   overflow_y='visible'))

# Creates System Parameter Controls (e.g. orbital property controls)
sys_title_default = '<b>System Parameters</b>:'
sys_title_scaling = '<b>System Parameters</b> (<B style="color:red">Objects\' sizes are NOT TO SCALE!</B>):'
if (exosystem_view.multiplier > 1):
    sys_title = widgets.HTML(value=sys_title_scaling, layout=widgets.Layout(overflow='visible'))
else:
    sys_title = widgets.HTML(value=sys_title_default, layout=widgets.Layout(overflow='visible'))
    
starorbit_controls = widgets.VBox([sys_title, 
                                   semimajor_cntl,
                                   widgets.HBox([spacer, widgets.VBox([P_output])]),
                                   ecc_cntl,  
                                   incl_cntl, 
                                   phi_cntl, 
                                   spacer],
                                  layout=widgets.Layout(overflow_x='visible', overflow_y='visible'))

# Assemble items in left column
sim_view = widgets.HBox([widgets.HTML('<h3>Model View</h3>'), exosystem_renderer],  
                                         layout=widgets.Layout(width=ControlColWidth,      
                                                               flex_flow='column',
                                                               align_items='center',
                                                               align_contents='center',
                                                               overflow_x='visible',
                                                               overflow_y='visible'))
left_column = widgets.VBox([sim_view, phase_controls, starorbit_controls], 
                           layout=widgets.Layout(width=ControlColWidth,
                                                 overflow_x='visible',
                                                 overflow_y='visible'))


##
## Build Controls on right side
##

# Compute light curve or radial velocity curve and select which to display initially
graph_update()

# Creates Stellar Mass Controls
star_title = widgets.HTML('<b>Viewer Properties</b>:', layout=widgets.Layout(overflow='visible'))
starmass_title = widgets.HTML(value="<b>Star Parameters</b>", layout=widgets.Layout(overflow='visible'))
exomass_title = widgets.HTML(value="<b>Planet Parameters</b>", layout=widgets.Layout(overflow='visible'))
star_controls = widgets.VBox([star_title, draw_grid, scale_force, zoom_slider, starmass_title, starmass_cntl, star_info, 
                               exomass_title, exomass_cntl, exo_info],
                            layout=widgets.Layout(overflow_x='visible', overflow_y='visible'))
    
# Creates a vertical box for the right control panel
graph_box = widgets.VBox([fig_selector, graph_fig], 
                            layout=widgets.Layout(width=ControlColWidth,
                                                  overflow_x='visible', 
                                                  overflow_y='visible') )
right_column = widgets.VBox([graph_box, star_controls], 
                            layout=widgets.Layout(width=ControlColWidth,
                                                  overflow_x='visible', 
                                                  overflow_y='visible') )

# Places the figure, sliders, and output into a Vbox. The figure is 
# alone in the top, while the sliders and output are in a Hbox
# inside the bottom of the Vbox.
MainDisplay = widgets.HBox([left_column, spacer, right_column])

# Sets the dimensions of the box. Sets the entire width and the height of 
# just the top.
MainDisplay.layout.width = EntireWidth
MainDisplay.layout.overflow = 'visible'
display(MainDisplay)

##
## Turn on interactivity by linking sliders to functions changing the simulation.
##

# This control determines which graph to display
fig_selector.observe(graph_update, names=['value'])

# If mass changes, we also need to check for radius and temperature changes
starmass_link = traitlets.directional_link((starmass_slider, 'value'), (bsm, 'mass1'))
exomass_link = traitlets.directional_link((exomass_in_MSun, 'value'), (bsm, 'mass2'))

# Handle changes in orbit directly in binary star model object
a_link = traitlets.directional_link((semimajor_slider, 'value'), (bsm, 'a'))
ecc_link = traitlets.directional_link((ecc_slider, 'value'), (bsm, 'e'))
phi_link = traitlets.directional_link((phi_slider, 'value'), (bsm, 'phi'))

# Call property_update if anything that potentially changes the orbit or luminosity is changed
starmass_slider.observe(property_update, names=['value'])
exomass_slider.observe(property_update, names=['value'])
semimajor_slider.observe(property_update, names=['value'])
ecc_slider.observe(property_update, names=['value'])
phi_slider.observe(property_update, names=['value'])

# Handle changes in inclination directly in binary star model and binary star viewer
incl_link = traitlets.directional_link((incl_slider, 'value'), (bsm, 'incl'))
incl_viewlink = traitlets.directional_link((incl_slider, 'value'), (exosystem_view, 'incl'))
incl_slider.observe(inclination_update, names=['value'])

# This slider just changes what phase of the orbit to display
phase_link = traitlets.directional_link((phase_slider, 'value'), (exosystem_view, 't_idx'))
phase_slider.observe(position_update, names=['value'])

# If user wants to switch scaling, link that here
orbit_change_link = traitlets.directional_link((scale_force, 'value'), (exosystem_view, 'lock_scale'))
orbit_change_link = traitlets.directional_link((draw_grid, 'value'), (exosystem_view, 'draw_grid'))
orbit_change_link = traitlets.directional_link((draw_grid, 'value'), (exosystem_view, 'draw_orbits'))
scale_force.observe(scene_update, names=['value'])
draw_grid.observe(scene_update, names=['value'])
zoom_slider.observe(zoom_update, names=['value'])

# if Binary Star Model orbit model changes, trigger the Binary Star Viewer to change
orbit_change_link = traitlets.directional_link((bsm, 'mdl_counter'), (exosystem_view, 'mdl_counter'))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08617942500865951, 'fraction_numerical': 0.011130354462533194, 'mean_word_length': 1.83249394989862, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 267, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '191508', 'license': 'MIT', 'n_tokens_mistral': 11773, 'n_tokens_neox': 11062, 'n_words': 2690, 'path': 'Interactives/Exoplanet_Sim.ipynb', 'repo_name': 'greywormz/Astrophysics', 'size': '43317'}