Exemplo n.º 1
0
 def test_comparison(self):
     lq1 = u.Magnitude(np.arange(1., 4.) * u.Jy)
     lq2 = u.Magnitude(2. * u.Jy)
     assert np.all((lq1 > lq2) == np.array([True, False, False]))
     assert np.all((lq1 == lq2) == np.array([False, True, False]))
     lq3 = u.Dex(2. * u.Jy)
     assert np.all((lq1 > lq3) == np.array([True, False, False]))
     assert np.all((lq1 == lq3) == np.array([False, True, False]))
     lq4 = u.Magnitude(2. * u.m)
     assert not (lq1 == lq4)
     assert lq1 != lq4
     with pytest.raises(u.UnitsError):
         lq1 < lq4
     q5 = 1.5 * u.Jy
     assert np.all((lq1 > q5) == np.array([True, False, False]))
     assert np.all((q5 < lq1) == np.array([True, False, False]))
     with pytest.raises(u.UnitsError):
         lq1 >= 2. * u.m
     with pytest.raises(u.UnitsError):
         lq1 <= lq1.value * u.mag
     # For physically dimensionless, we can compare with the function unit.
     lq6 = u.Magnitude(np.arange(1., 4.))
     fv6 = lq6.value * u.mag
     assert np.all(lq6 == fv6)
     # but not some arbitrary unit, of course.
     with pytest.raises(u.UnitsError):
         lq6 < 2. * u.m
Exemplo n.º 2
0
 def changed_value(self, ref, param, unit):
     new_val = self.refs[ref].value
     if unit is not None:
         new_val = new_val * unit
     if ref == "exp_slider":
         new_val = pre_encode(new_val.to(u.s))
     elif ref == "age_slider":
         new_val = pre_encode(u.Dex(10.**new_val * u.Gyr))
     elif ref == "metallicity_slider":
         new_val = pre_encode(u.Dex(new_val))
     else:
         new_val = pre_encode(new_val)
     if ref == "crowding_slider":
         print(param, getattr(self, param), new_val)
     if getattr(self, param) != new_val:
         setattr(self, param, new_val)
         return True
     return False
Exemplo n.º 3
0
class TestLogQuantityArithmetic:
    @pytest.mark.parametrize('other', [
        2.4 * u.mag(), 12.34 * u.ABmag,
        u.Magnitude(3.45 * u.Jy),
        u.Dex(3.),
        u.Dex(np.linspace(3000, 5000, 10) * u.Angstrom),
        u.Magnitude(6.78, 2. * u.mag)
    ])
    @pytest.mark.parametrize('fac', [1., 2, 0.4])
    def test_multiplication_division(self, other, fac):
        """Check that multiplication and division works as expectes"""

        lq_sf = fac * other
        assert lq_sf.unit.physical_unit == other.unit.physical_unit**fac
        assert_allclose(lq_sf.physical, other.physical**fac)

        lq_sf = other * fac
        assert lq_sf.unit.physical_unit == other.unit.physical_unit**fac
        assert_allclose(lq_sf.physical, other.physical**fac)

        lq_sf = other / fac
        assert lq_sf.unit.physical_unit**fac == other.unit.physical_unit
        assert_allclose(lq_sf.physical**fac, other.physical)

        lq_sf = other.copy()
        lq_sf *= fac
        assert lq_sf.unit.physical_unit == other.unit.physical_unit**fac
        assert_allclose(lq_sf.physical, other.physical**fac)

        lq_sf = other.copy()
        lq_sf /= fac
        assert lq_sf.unit.physical_unit**fac == other.unit.physical_unit
        assert_allclose(lq_sf.physical**fac, other.physical)

    def test_more_multiplication_division(self):
        """Check that multiplication/division with other quantities is only
        possible when the physical unit is dimensionless, and that this turns
        the result into a normal quantity."""
        lq = u.Magnitude(np.arange(1., 11.) * u.Jy)

        with pytest.raises(u.UnitsError):
            lq * (1. * u.m)

        with pytest.raises(u.UnitsError):
            (1. * u.m) * lq

        with pytest.raises(u.UnitsError):
            lq / lq

        for unit in (u.m, u.mag, u.dex):
            with pytest.raises(u.UnitsError):
                lq / unit

        lq2 = u.Magnitude(np.arange(1, 11.))

        with pytest.raises(u.UnitsError):
            lq2 * lq

        with pytest.raises(u.UnitsError):
            lq2 / lq

        with pytest.raises(u.UnitsError):
            lq / lq2

        lq_sf = lq.copy()

        with pytest.raises(u.UnitsError):
            lq_sf *= lq2
        # ensure that nothing changed inside
        assert (lq_sf == lq).all()

        with pytest.raises(u.UnitsError):
            lq_sf /= lq2
        # ensure that nothing changed inside
        assert (lq_sf == lq).all()

        # but dimensionless_unscaled can be cancelled
        r = lq2 / u.Magnitude(2.)
        assert r.unit == u.dimensionless_unscaled
        assert np.all(r.value == lq2.value / 2.)

        # with dimensionless, normal units OK, but return normal quantities
        tf = lq2 * u.m
        tr = u.m * lq2
        for t in (tf, tr):
            assert not isinstance(t, type(lq2))
            assert t.unit == lq2.unit.function_unit * u.m
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(lq2.unit.physical_unit)

        t = tf / (50. * u.cm)
        # now we essentially have the same quantity but with a prefactor of 2
        assert t.unit.is_equivalent(lq2.unit.function_unit)
        assert_allclose(t.to(lq2.unit.function_unit), lq2._function_view * 2)

    @pytest.mark.parametrize('power', (2, 0.5, 1, 0))
    def test_raise_to_power(self, power):
        """Check that raising LogQuantities to some power is only possible when
        the physical unit is dimensionless, and that conversion is turned off
        when the resulting logarithmic unit (say, mag**2) is incompatible."""
        lq = u.Magnitude(np.arange(1., 4.) * u.Jy)

        if power == 0:
            assert np.all(lq**power == 1.)
        elif power == 1:
            assert np.all(lq**power == lq)
        else:
            with pytest.raises(u.UnitsError):
                lq**power

        # with dimensionless, it works, but falls back to normal quantity
        # (except for power=1)
        lq2 = u.Magnitude(np.arange(10.))

        t = lq2**power
        if power == 0:
            assert t.unit is u.dimensionless_unscaled
            assert np.all(t.value == 1.)
        elif power == 1:
            assert np.all(t == lq2)
        else:
            assert not isinstance(t, type(lq2))
            assert t.unit == lq2.unit.function_unit**power
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(u.dimensionless_unscaled)

    def test_error_on_lq_as_power(self):
        lq = u.Magnitude(np.arange(1., 4.) * u.Jy)
        with pytest.raises(TypeError):
            lq**lq

    @pytest.mark.parametrize('other', pu_sample)
    def test_addition_subtraction_to_normal_units_fails(self, other):
        lq = u.Magnitude(np.arange(1., 10.) * u.Jy)
        q = 1.23 * other
        with pytest.raises(u.UnitsError):
            lq + q

        with pytest.raises(u.UnitsError):
            lq - q

        with pytest.raises(u.UnitsError):
            q - lq

    @pytest.mark.parametrize(
        'other', (1.23 * u.mag, 2.34 * u.mag(), u.Magnitude(
            3.45 * u.Jy), u.Magnitude(4.56 * u.m), 5.67 * u.Unit(2 * u.mag),
                  u.Magnitude(6.78, 2. * u.mag)))
    def test_addition_subtraction(self, other):
        """Check that addition/subtraction with quantities with magnitude or
        MagUnit units works, and that it changes the physical units
        appropriately."""
        lq = u.Magnitude(np.arange(1., 10.) * u.Jy)
        other_physical = other.to(getattr(other.unit, 'physical_unit',
                                          u.dimensionless_unscaled),
                                  equivalencies=u.logarithmic())

        lq_sf = lq + other
        assert_allclose(lq_sf.physical, lq.physical * other_physical)

        lq_sr = other + lq
        assert_allclose(lq_sr.physical, lq.physical * other_physical)

        lq_df = lq - other
        assert_allclose(lq_df.physical, lq.physical / other_physical)

        lq_dr = other - lq
        assert_allclose(lq_dr.physical, other_physical / lq.physical)

    @pytest.mark.parametrize('other', pu_sample)
    def test_inplace_addition_subtraction_unit_checks(self, other):
        lu1 = u.mag(u.Jy)
        lq1 = u.Magnitude(np.arange(1., 10.), lu1)
        with pytest.raises(u.UnitsError):
            lq1 += other

        assert np.all(lq1.value == np.arange(1., 10.))
        assert lq1.unit == lu1

        with pytest.raises(u.UnitsError):
            lq1 -= other

        assert np.all(lq1.value == np.arange(1., 10.))
        assert lq1.unit == lu1

    @pytest.mark.parametrize(
        'other', (1.23 * u.mag, 2.34 * u.mag(), u.Magnitude(
            3.45 * u.Jy), u.Magnitude(4.56 * u.m), 5.67 * u.Unit(2 * u.mag),
                  u.Magnitude(6.78, 2. * u.mag)))
    def test_inplace_addition_subtraction(self, other):
        """Check that inplace addition/subtraction with quantities with
        magnitude or MagUnit units works, and that it changes the physical
        units appropriately."""
        lq = u.Magnitude(np.arange(1., 10.) * u.Jy)
        other_physical = other.to(getattr(other.unit, 'physical_unit',
                                          u.dimensionless_unscaled),
                                  equivalencies=u.logarithmic())
        lq_sf = lq.copy()
        lq_sf += other
        assert_allclose(lq_sf.physical, lq.physical * other_physical)

        lq_df = lq.copy()
        lq_df -= other
        assert_allclose(lq_df.physical, lq.physical / other_physical)

    def test_complicated_addition_subtraction(self):
        """For fun, a more complicated example of addition and subtraction."""
        dm0 = u.Unit('DM', 1. / (4. * np.pi * (10. * u.pc)**2))
        DMmag = u.mag(dm0)
        m_st = 10. * u.STmag
        dm = 5. * DMmag
        M_st = m_st - dm
        assert M_st.unit.is_equivalent(u.erg / u.s / u.AA)
        assert np.abs(M_st.physical / (m_st.physical * 4. * np.pi *
                                       (100. * u.pc)**2) - 1.) < 1.e-15
Exemplo n.º 4
0
class CMD(SYOTool):

    tool_prefix = "cmd"

    save_models = ["telescope", "camera", "exposure"]

    save_params = {
        "exptime": None,  #slider value                       #NOT YET UPDATED
        "snratio": None,  #slider value
        "age": None,  #slider value
        "crowding": None,  #slider value
        "distance": None,  #slider value,
        "metallicity": None,  #slider value
        "noise": None,  #slider value
        "spectrum_type": ("exposure", "sed_id"),
        "aperture": ("telescope", "aperture"),
        "user_prefix": None
    }

    save_dir = os.path.join(os.environ['LUVOIR_SIMTOOLS_DIR'], 'saves')

    #must include this to set defaults before the interface is constructed
    tool_defaults = {
        'exptime': pre_encode(3600.0 * u.s),
        'snratio': pre_encode(30.0 * u.electron**0.5),
        'age': pre_encode(u.Dex(10.0 * u.Gyr)),
        'crowding':
        pre_encode(20.0 * u.dimensionless_unscaled),  #u.ABmag / u.arcsec**2),
        'distance': pre_encode(1.0 * u.Mpc),
        'metallicity': pre_encode(u.Dex(0.0)),
        'noise': pre_encode(500.0 * u.dimensionless_unscaled),
        'aperture': pre_encode(15.0 * u.m),
        'spectrum_type': 'fab'
    }

    verbose = True

    def tool_preinit(self):
        """
        Pre-initialize any required attributes for the interface.
        """
        #set up holoviews & load data for datashader
        #note that right now the data is read from a pickle, not loaded separately;
        #I'm leaving load_dataset.py in the directory, but it's not used currently
        self.dataframe = pandas.read_pickle(
            os.path.join(basedir, 'data', 'cmd_frame_large_no_noise.pkl'))
        self.cmap_picker = ColormapPicker(rename={'colormap': 'cmap'}, name='')

        #initialize engine objects
        self.telescope = Telescope()
        self.camera = Camera()
        self.exposure = Exposure()
        self.telescope.add_camera(self.camera)
        self.camera.add_exposure(self.exposure)

        #set interface variables
        self.help_text = help_text

        #Formatting & interface stuff:
        self.format_string = interface_format
        self.interface_file = os.path.join(script_dir, "interface.yaml")

        #For saving calculations
        self.current_savefile = ""
        self.overwrite_save = False

    def tool_postinit(self):
        #update default exposure based on tool_defaults
        self.update_exposure_params()

    #Calculation methods

    @staticmethod
    def add_noise(new_frame, noise_scale):
        rmag = new_frame.rmag
        gmag = new_frame.gmag
        noise_basis = 10. / 10.**((30. + rmag) / 5.)  # mind the 10!
        r_noise = np.random.normal(0.0, noise_scale,
                                   np.size(rmag)) * noise_basis
        g_noise = np.random.normal(0.0, noise_scale,
                                   np.size(rmag)) * noise_basis
        new_frame.rmag = rmag + r_noise
        new_frame.gmag = gmag + g_noise
        new_frame.grcolor = np.clip(rmag - gmag, a_min=-1.2, a_max=4.2)
        return new_frame

    def select_dataframe(self, metallicity, age):
        selection = lambda frame: (frame.metalindex == metallicity) & (
            frame.ageindex == age)
        return self.dataframe.loc[selection]

    def select_stars(
        self, obj, age, metallicity, noise
    ):  # received "obj" of type "Points" and "age" of type ordinary float
        #"obj" incoming is the points object
        if self.verbose:
            print("age / metallicity / noise inside select_stars", age,
                  metallicity, noise)
        new_frame = self.select_dataframe(metallicity, age)
        noise_frame = self.add_noise(new_frame, float(noise))
        cmd_points = hv.Points(noise_frame, kdims=['grcolor', 'rmag'])
        return cmd_points

    @property
    def _derived_snrs(self):
        new_snrs = np.zeros(5, dtype=float)
        mags = self.refs["cmd_mag_source"].data["mags"]
        for m, mag in enumerate(mags):
            self.exposure.renorm_sed(mag * u.ABmag)
            new_snrs[m] = self.exposure.recover('snr')[4].value

        return new_snrs

    @property
    def _derived_snrs_labels(self):
        return self._derived_snrs.astype('|S4')

    def crowding_limit(self, crowding_apparent_magnitude, distance):
        """
        Calculate the crowding limit.
        """
        aperture = pre_decode(self.aperture)

        g_solar = 4.487
        stars_per_arcsec_limit = 10. * (aperture / 2.4)**2  # (JD1)

        distmod = 5. * np.log10((distance + 1e-5) * 1e6) - 5.  #why this fudge?

        g = np.array(
            -self.dataframe.gmag
        )  # absolute magnitude in the g band - the -1 corrects for the sign flip in load_datasets (for plotting)
        g.sort(
        )  # now sorted from brightest to faintest in terms of absolute g magnitude
        luminosity = 10.**(-0.4 * (g - g_solar))
        total_luminosity_in_lsun = np.sum(luminosity)
        number_of_stars = np.full_like(
            g, 1.0
        )  # initial number of stars in each "bin" - a list of 10,000 stars

        total_absolute_magnitude = -2.5 * np.log10(
            total_luminosity_in_lsun) + g_solar
        apparent_brightness_at_this_distance = total_absolute_magnitude + distmod

        scale_factor = 10.**(-0.4 * (crowding_apparent_magnitude -
                                     apparent_brightness_at_this_distance))
        cumulative_number_of_stars = np.cumsum(
            scale_factor *
            number_of_stars)  # the cumulative run of luminosity in Lsun

        crowding_limit = np.interp(stars_per_arcsec_limit.value,
                                   cumulative_number_of_stars, g)

        return crowding_limit

    #Control methods

    def quantize_slider(self, ref, precision, minval, step):
        """
        A utility function for quantizing the value of a slider.
        """
        val = Decimal(self.refs[ref].value).quantize(Decimal(precision))
        return int(
            ((val - Decimal(minval)) / Decimal(step)).to_integral_exact())

    def age_slider_update(self):
        """
        Send an event to the age stream.
        """
        new_age = self.quantize_slider("age_slider", '0.01', '5.5', '0.05')
        self.refs["age_stream"].event(age=new_age)
        if self.verbose:
            print("Age selected by slider = {}".format(new_age))

    def metallicity_slider_update(self):
        """
        Send an event to the metallicity stream. Using Decimal to handle 
        quantization.
        """
        new_metallicity = self.quantize_slider("metallicity_slider", '0.1',
                                               '-2.0', '0.5')
        self.refs["metallicity_stream"].event(metallicity=new_metallicity)
        if self.verbose:
            print(
                "Metallicity selected by slider = {}".format(new_metallicity))

    def noise_slider_update(self):
        """
        Send an event to the noise stream. This slider is in steps of 50, so
        we can just quantize with int() instead of using Decimal.
        """
        ival = int(self.refs["noise_slider"].value)
        if self.verbose:
            print("Inside noise_slider, will scale to: {}".format(ival))
        self.refs["noise_stream"].event(noise=ival)

    def distance_slider_update(self):
        """
        Update the magnitude label source for the new distance.
        """
        val = self.refs["distance_slider"].value
        distmod = 5. * np.log10((val + 1e-5) * 1e6) - 5.
        new_mags = distmod + np.array([10., 5., 0., -5., -10.])
        mlsource = self.refs['cmd_mag_source']
        mlsource.data['mags'] = new_mags
        mlsource.data['text'] = new_mags.astype('|S4')

    def crowding_slider_update(self):
        """
        Update the crowding from the slider input.
        """
        crowding_mag = self.refs["crowding_slider"].value
        distance = self.refs["distance_slider"].value

        nstars_per_arcsec = int(10. * (self.refs['ap_slider'].value / 2.4)**2)
        crowding_limit = self.crowding_limit(crowding_mag, distance)
        confsource = self.refs["cmd_conf_source"]
        confsource.data = {
            'top': [-crowding_limit],
            'textx': [-0.8],
            'texty': [-crowding_limit - 0.2],
            'text':
            ['Crowding: ({} stars / sq. arsec)'.format(nstars_per_arcsec)]
        }

    def update_exposure_params(self):
        """
        Update the exposure parameters.
        """

        new_aper = self.refs["ap_slider"].value * u.m
        new_expt = (self.refs["exp_slider"].value * u.h).to(u.s)
        new_snr = self.refs["snr_slider"].value * u.electron**0.5

        self.aperture = pre_encode(new_aper)
        self.exptime = pre_encode(new_expt)
        self.snratio = pre_encode(new_snr)

        self.telescope.aperture = self.aperture

    def exposure_update(self):
        """
        Update the exposure when sliders are updated.
        """
        self.update_exposure_params()

        self.exposure.unknown = 'snr'
        self.exposure.exptime = pre_decode(self.exptime)

        new_snrs = self._derived_snrs
        etcsource = self.refs["cmd_etc_source"]
        mlsource = self.refs["cmd_mag_source"]

        etcsource.data = {
            "mags": mlsource.data["mags"],
            "snr": new_snrs,
            "snr_label": new_snrs.astype('|S4'),
            'x': np.full(5, 3.2).tolist(),
            'y': np.arange(-10.4, 10., 5., dtype=float).tolist()
        }

        if self.verbose:
            print("mag_values in exposure_update: {}".format(
                etcsource.data['y']))
            print("new_snrs in exposure_update: {}".format(
                etcsource.data['snr']))

        noise_scale_factor = int(
            10000. / new_snrs[1]
        )  # divide an number by the S/N at AB = 5 absolute - set up to make it come out right
        self.refs["noise_stream"].event(noise=int(noise_scale_factor))

    def sn_slider_update(self):
        """
        Update exposure when SNR slider is changed.
        """
        new_sn = pre_decode(self.snratio)
        new_expt = pre_decode(self.exptime)
        #new_sn = self.refs["snr_slider"].value
        #new_expt = self.refs["exp_slider"].value
        if self.verbose:
            print("calling sn_updater with sn = {} and exptime {}".format(
                new_sn, new_expt))

        self.exposure.unknown = "magnitude"
        self.exposure.exptime = new_expt  #pre_decode(self.exptime)
        self.exposure.snr = new_sn  #pre_decode(self.snratio)

        vmag = self.exposure.recover('magnitude')[4].value
        distance = pre_decode(self.distance)
        distmod = 5. * np.log10((distance.value + 1e-5) * 1e6) - 5.

        lmsource = self.refs["cmd_lim_source"]
        lmsource.data = {
            'mags': [distmod - vmag - 0.4],
            'maglabel': ["{:4.1f}".format(vmag)],
            'sn': ["{}".format(new_sn.value)],
            'x_mag': [3.8],
            'x_sn': [3.2]
        }  # the 0.4 is just for display purposes (text alignment)

    def changed_value(self, ref, param, unit):
        new_val = self.refs[ref].value
        if unit is not None:
            new_val = new_val * unit
        if ref == "exp_slider":
            new_val = pre_encode(new_val.to(u.s))
        elif ref == "age_slider":
            new_val = pre_encode(u.Dex(10.**new_val * u.Gyr))
        elif ref == "metallicity_slider":
            new_val = pre_encode(u.Dex(new_val))
        else:
            new_val = pre_encode(new_val)
        if ref == "crowding_slider":
            print(param, getattr(self, param), new_val)
        if getattr(self, param) != new_val:
            setattr(self, param, new_val)
            return True
        return False

    def controller(self, attr, old, new):
        """
        Master controller callback. Instead of having a bunch of different
        callbacks, instead we're going to track which things have changed,
        and then call the individual update methods that are required.
        """

        #Grab values from the inputs, tracking which have changed

        params = {
            'exp': ('exp_slider', 'exptime', u.hour),
            'age': ('age_slider', 'age', None),
            'snr': ('snr_slider', 'snratio', u.electron**0.5),
            'crowd': ('crowding_slider', 'crowding',
                      u.dimensionless_unscaled),  # u.ABmag / u.arcsec**2),
            'dist': ('distance_slider', 'distance', u.Mpc),
            'metal': ('metallicity_slider', 'metallicity', None),
            'noise': ('noise_slider', 'noise', u.dimensionless_unscaled),
            'aper': ('ap_slider', 'aperture', u.m)
        }

        updated = set(
            [par for par, args in params.items() if self.changed_value(*args)])

        #Call the requisite update methods
        if not updated.isdisjoint({'dist', 'aper', 'exp'}):
            self.exposure_update()
        if not updated.isdisjoint({'dist', 'aper', 'exp', 'crowd'}):
            self.crowding_slider_update()
        if not updated.isdisjoint({'dist', 'aper', 'exp', 'snr'}):
            self.sn_slider_update()
        if 'noise' in updated:
            self.noise_slider_update()
        if 'dist' in updated:
            self.distance_slider_update()
        if 'metal' in updated:
            self.metallicity_slider_update()
        if 'age' in updated:
            self.age_slider_update()

        #Now, all of the plot updates should be handled by stream events, I think...

    #NONE OF THESE MATTER UNTIL SAVE/LOAD IS ADDED

    def update_toggle(self, active):
        if active:
            self.refs["user_prefix"].value = self.user_prefix
            self.refs["user_prefix"].disabled = True
            self.refs["save_button"].disabled = False
            self.overwrite_save = True
        else:
            self.refs["user_prefix"].disabled = False
            self.refs["save_button"].disabled = False
            self.overwrite_save = False

    #Save and Load
    def save(self):
        """
        Save the current calculations.
        """

        #Check for an existing save file if we're overwriting
        if self.overwrite_save and self.current_savefile:
            self.current_savefile = self.save_file(self.current_savefile,
                                                   overwrite=True)
        else:
            #Set the user prefix from the bokeh interface
            prefix = self.refs["user_prefix"].value
            if not prefix.isalpha() or len(prefix) < 3:
                self.refs["save_message"].text = "Please include a prefix of at "\
                    "least 3 letters (and no other characters)."
                return
            self.user_prefix = prefix
            #Save the file:
            self.current_savefile = self.save_file()

        #Tell the user the filename or the error message.
        if not self.current_savefile:
            self.refs["save_message"].text = "Save unsuccessful; please " \
                "contact the administrators."
            return

        self.refs["save_message"].text = "This calculation was saved with " \
            "the ID {}".format(self.current_savefile)
        self.refs["update_save"].disabled = False

    def load(self):
        # Get the filename from the bokeh interface
        calcid = self.refs["load_filename"].value

        #Load the file
        code = self.load_file(calcid)

        if not code:  #everything went fine
            #Update the interface
            self.refs["update_save"].disabled = False
            self.current_save = calcid
            self.refs["exp_slider"].value = pre_decode(self.exptime).value
            self.refs["mag_slider"].value = pre_decode(
                self.renorm_magnitude).value
            self.refs["ap_slider"].value = pre_decode(self.aperture).value
            self.refs["snr_slider"].value = pre_decode(self.snratio).value
            temp = self.templates.index(self.spectrum_type)
            self.refs["template_select"].value = self.template_options[temp]
            self.controller(None, None, None)

        errmsg = [
            "Calculation ID {} loaded successfully.".format(calcid),
            "Calculation ID {} does not exist, please try again.".format(
                calcid),
            "Load unsuccessful; please contact the administrators.",
            "There was an error restoring the save state; please contact"
            " the administrators."
        ][code]
        if code == 0 and self.load_mismatch:
            errmsg += "<br><br><b><i>Load Mismatch Warning:</b></i> "\
                      "The saved model parameters did not match the " \
                      "parameter values saved for this tool. Saved " \
                      "model values were given priority."
        self.refs["load_message"].text = errmsg