{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Small Angle Approximation Interactive\n", "\n", "This interactive can be used to explore the relationship between an object's size, its distance, and its observed angular size. When an object is far away compared to its size, astronomers use the *small angle approximation* to simplify the relationship.\n", "\n", "There are two control sliders: the first for the size of the object (s) and the second for the distance from Earth to the object (d). The interactive uses both the \"exact\" equation and small angle approximation to estimate the angular size of the object:\n", "\n", "$$\\theta_{exact} = 2\\arctan\\left(\\frac{s}{2d}\\right) \\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\;\\; \\theta_{approx} = \\frac{s}{d}$$\n", "\n", "**Note**: The above espressions give the angle $\\theta$ in radians. To get an angle in degrees, we must multiply by the conversion factor $\\frac{180^{\\circ}}{\\pi}$ (because there are $2\\pi$ radians or $360^{\\circ}$ in a circle)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Originally created on June 13, 2018 by Samuel Holen\n", "\n", "import ipywidgets as widgets\n", "import numpy as np\n", "import bqplot.pyplot as bq\n", "from IPython.display import display" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def approx_theta(s,d):\n", " # Small angle approximation equation\n", " return s/d\n", "\n", "def exact_theta(s,d):\n", " # Exact equation for theta\n", " return 2*np.arctan(s/(2*d))\n", "\n", "def circle(r,d=0.):\n", " # Creates a circle given a radius r and displacement along the \n", " # x-axis d \n", " theta = np.linspace(0,2*np.pi,1000)\n", " return (r*np.cos(theta)+d,r*np.sin(theta))\n", "\n", "def par_circ(r,theta0,theta1,d=0.,h=0.):\n", " theta = np.linspace(theta0,theta1,1000)\n", " return (r*np.cos(theta)+d,r*np.sin(theta)+h)\n", "\n", "def ellipse(a,b,d=0.):\n", " # Creates an ellipse centered at (d,0) with a semimajor axis of 'a'\n", " # in the x direction and 'b' in the y direction\n", " theta = np.linspace(0,2*np.pi,1000)\n", " return (a*np.cos(theta)+d,b*np.sin(theta))\n", "\n", "def update(change=None):\n", " # Update the display.\n", " D1.y = [0,h_slider.value/2]\n", " D2.y = [0,-h_slider.value/2]\n", " D1.x = [0,d_slider.value]\n", " D2.x = [0,d_slider.value]\n", " ref.x = [0,d_slider.value]\n", " # Note that the ellipse is used so that the display needn't be a square.\n", " X_new, Y_new = circle(h_slider.value/2, d_slider.value)\n", " Object.x = X_new\n", " Object.y = Y_new\n", " \n", " # Update the resulting angles\n", " theta_approx = 180/np.pi*approx_theta(h_slider.value,d_slider.value)\n", " theta_exact = 180/np.pi*exact_theta(h_slider.value,d_slider.value)\n", " \n", " approx_eqn.value='

\"Small Angle\" Equation:
{:.4f}° = (180°/π) * ({:.1f} / {:.1f})

'.format(theta_approx,h_slider.value,d_slider.value)\n", " exact_eqn.value ='

\"Exact\" Equation:
{:.4f}° = 2 arctan( {:.1f} / 2*{:.1f} )

'.format(theta_exact,h_slider.value,d_slider.value) \n", " difference_eqn.value='

Difference:
{:.4f}° ({:.2f}%)

'.format(abs(theta_exact-theta_approx), 100*(abs(theta_exact-theta_approx)/theta_exact))\n", " \n", " arc_loc = (d_slider.value - h_slider.value/2)/3\n", " angle_loc = exact_theta(h_slider.value,d_slider.value)/2\n", " xc,yc = par_circ(r=arc_loc, theta0=2*np.pi-angle_loc, theta1=2*np.pi+angle_loc)\n", "\n", " angle_ex.x = xc\n", " angle_ex.y = yc" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h_slider = widgets.FloatSlider(\n", " value=5,\n", " min=0.1,\n", " max=30.05,\n", " step=0.1,\n", " disabled=False,\n", " continuous_update=True,\n", " orientation='horizontal',\n", " readout=True,\n", " readout_format='.1f',\n", ")\n", "d_slider = widgets.FloatSlider(\n", " value=50,\n", " min=20.,\n", " max=100,\n", " step=0.1,\n", " disabled=False,\n", " continuous_update=True,\n", " orientation='horizontal',\n", " readout=True,\n", " readout_format='.1f',\n", ")\n", "\n", "theta_approx = 180/np.pi*approx_theta(h_slider.value,d_slider.value)\n", "theta_exact = 180/np.pi*exact_theta(h_slider.value,d_slider.value)\n", "\n", "# Create labels for sliders\n", "h_label = widgets.Label(value='Size of object (s)')\n", "d_label = widgets.Label(value='Distance to object (d)')\n", "\n", "# Create text display\n", "approx_eqn = widgets.HTML(value='

\"Small Angle\" Equation:
{:.4f}° = (180°/π) * ({:.1f} / {:.1f})

'.format(theta_approx,h_slider.value,d_slider.value))\n", "exact_eqn = widgets.HTML(value='

\"Exact\" Equation:
{:.4f}° = 2 arctan( {:.1f} / 2*{:.1f} )

'.format(theta_exact,h_slider.value,d_slider.value))\n", "difference_eqn = widgets.HTML(value='

Difference:
{:.4f}° ({:.2f}%)

'.format(abs(theta_exact-theta_approx), 100*(abs(theta_exact-theta_approx)/theta_exact)))\n", " \n", "blank = widgets.Label(value='')\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## PLOT/FIGURE ##\n", "\n", "# Sets axis scale for x and y to \n", "sc_x = bq.LinearScale(min=-5, max=115)\n", "sc_y = bq.LinearScale(min=-26, max=26)\n", "\n", "# Get the range to work with\n", "x_range = sc_x.max - sc_x.min\n", "y_range = sc_y.max - sc_y.min\n", "\n", "# Initial height and distance of star\n", "init_h = h_slider.value\n", "init_d = d_slider.value\n", "\n", "# Note that the ellipse is used so that the display needn't be a square.\n", "# Creates a circular 'star'\n", "X,Y = circle(r=init_h/2, d=init_d)\n", "\n", "# Sets up the axes, grid-lines are set to black so that they blend in with the background.\n", "ax_x = bq.Axis(scale=sc_x, grid_color='white', num_ticks=0)\n", "ax_y = bq.Axis(scale=sc_y, orientation='vertical', grid_color='white', num_ticks=0)\n", "\n", "# Draws the lines to the top and bottom of the star respectively\n", "D1 = bq.Lines(x=[0,init_d], y=[0,init_h/2], scales={'x': sc_x, 'y': sc_y}, colors=['white'])\n", "D2 = bq.Lines(x=[0,init_d], y=[0,-init_h/2], scales={'x': sc_x, 'y': sc_y}, colors=['white'])\n", "\n", "# Creates the star\n", "Object = bq.Lines(scales={'x': sc_x, 'y': sc_y}, x=X, y=Y, colors=['blue'], \n", " fill='inside', fill_colors=['blue'])\n", "\n", "# Creates a reference line.\n", "ref = bq.Lines(x=[0,init_d], y=[0,0], scales={'x': sc_x, 'y': sc_y}, colors=['white'], line_style='dashed')\n", "\n", "arc_loc = (init_d - init_h/2)/3\n", "angle_loc = exact_theta(init_h,init_d)/2\n", "xc,yc = par_circ(r=arc_loc, theta0=2*np.pi-angle_loc, theta1=2*np.pi+angle_loc)\n", "\n", "angle_ex = bq.Lines(x=xc, y=yc, scales={'x': sc_x, 'y': sc_y}, colors=['white'])\n", "\n", "angle_label = bq.Label(x=[2], y=[0], scales={'x': sc_x, 'y': sc_y},\n", " text=[r'$$\\theta$$'], default_size=15, font_weight='bolder',\n", " colors=['white'], update_on_move=False)\n", "\n", "# Update the the plot/display\n", "h_slider.observe(update, names=['value'])\n", "d_slider.observe(update, names=['value'])\n", "\n", "# Creates the figure. The background color is set to black so that it looks like 'space.' Also,\n", "# removes the default y padding.\n", "fig = bq.Figure(title='Small Angle Approximation', marks=[Object,D1,D2,angle_ex], axes=[ax_x, ax_y], \n", " padding_x=0, padding_y=0, animation=100, background_style={'fill' : 'black'})\n", "\n", "# Display to the screen\n", "\n", "# Set up the figure\n", "fig_width = 750\n", "fig.layout.width = '{:.0f}px'.format(fig_width)\n", "fig.layout.height = '{:.0f}px'.format(fig_width/2)\n", "\n", "# Set up the bottom part containing the controls and display of equations\n", "h_box = widgets.VBox([h_label, h_slider])\n", "d_box = widgets.VBox([d_label, d_slider])\n", "slide_box = widgets.HBox([h_box, d_box])\n", "h_box.layout.width = '{:.0f}px'.format(fig_width/2)\n", "d_box.layout.width = '{:.0f}px'.format(fig_width/2)\n", "\n", "eqn_box = widgets.HBox([exact_eqn, approx_eqn, difference_eqn])\n", "exact_eqn.layout.width = '{:.0f}px'.format(fig_width/3)\n", "approx_eqn.layout.width = '{:.0f}px'.format(fig_width/3)\n", "difference_eqn.layout.width = '{:.0f}px'.format(fig_width/3)\n", "\n", "BOX = widgets.VBox([fig, slide_box, eqn_box])\n", "display(BOX)" ] } ], "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 }