Model Characterization

Using Timbre to Characterize a Model The Original Way

Now that the methodology behind how Timbre calculates a single balance time has been presented, the next step is to expand this capability to characterize a model by varying the input parameters.

First, we focus on varying the dwell #1 and dwell #2 pitch values, while keeping a fixed Dwell #1 value of 20000s. This will give us a picture of the value of a 20Ks cold initial dwell in terms of yielded hot time, or the cost of a 20000s hot initial dwell in terms of cooling time.

[1]:
import numpy as np
from cxotime import CxoTime
import astropy
import plotly.io as pio
from IPython.core.display import display, HTML
import pandas as pd

from timbre import (
    get_local_model,
    run_state_pairs,
    run_profile,
    calc_binary_schedule,
    BalanceAACCCDPT,
    get_limited_results,
    get_offset_results,
    BalanceFPTEMP_11,
    BalancePM2THV1T,
    f_to_c,
    Composite,
)
from plot_code import *
[2]:
msid = "aacccdpt"
limit = -6.5
date = "2021:001:00:00:00"
aca_model_spec, aca_md5 = get_local_model("../timbre/tests/data/aca_spec.json")
model_init = {"aacccdpt": -6.5, "aca0": -6.5, "eclipse": False}
t_dwell1 = 20000.0  # Seconds

Instead of defining each pitch condition manually, they are defined programatically.

[3]:
state_pairs = [
    ({"pitch": p1}, {"pitch": p2})
    for p1 in range(45, 181, 5)
    for p2 in range(45, 181, 5)
]
[4]:
results = run_state_pairs(
    msid,
    aca_model_spec,
    model_init,
    limit,
    date,
    t_dwell1,
    state_pairs,
    limit_type="max",
)
Running simulations for state pair #: 1 out of 784.0
[5]:
table = astropy.table.Table(results)
table
[5]:
Table length=784
msiddatedatesecslimitt_dwell1t_dwell2min_tempmean_tempmax_tempmin_pseudomean_pseudomax_pseudoconvergedunconverged_hotunconverged_coldhotter_statecolder_statepitch1eclipse1pitch2eclipse2
str20str8float64float64float64float64float64float64float64float64float64float64boolboolboolint8int8int64boolint64bool
aacccdpt2021:001725846469.184-6.520000.0nan-12.701908209660902-12.662815058213544-12.638279647107051nannannanFalseFalseTrue1245False45False
aacccdpt2021:001725846469.184-6.520000.0nan-10.754262032742414-10.478496753866102-10.370522019334915nannannanFalseFalseTrue2145False50False
aacccdpt2021:001725846469.184-6.520000.0nan-8.798103778770468-8.285324033439833-8.099490257052867nannannanFalseFalseTrue2145False55False
aacccdpt2021:001725846469.184-6.520000.0112262.86182175252-7.215625886759161-6.8844363747404635-6.5nannannanTrueFalseFalse2145False60False
aacccdpt2021:001725846469.184-6.520000.053485.8401238784-7.017587314447216-6.7811388893354385-6.5nannannanTrueFalseFalse2145False65False
aacccdpt2021:001725846469.184-6.520000.034897.288613469536-6.916623611692971-6.716555616870003-6.5nannannanTrueFalseFalse2145False70False
aacccdpt2021:001725846469.184-6.520000.025688.36697490581-6.872076377956675-6.686879998434366-6.5nannannanTrueFalseFalse2145False75False
aacccdpt2021:001725846469.184-6.520000.021920.29340453202-6.854834583007448-6.6800153492971655-6.5nannannanTrueFalseFalse2145False80False
aacccdpt2021:001725846469.184-6.520000.019200.986701918977-6.830194229487504-6.661476900204302-6.5nannannanTrueFalseFalse2145False85False
aacccdpt2021:001725846469.184-6.520000.016975.82687247957-6.829009371685877-6.661592013463294-6.5nannannanTrueFalseFalse2145False90False
...............................................................
aacccdpt2021:001725846469.184-6.520000.0135864.2576042953-8.811710086766464-7.7269277505322504-6.5nannannanTrueFalseFalse21180False135False
aacccdpt2021:001725846469.184-6.520000.0nan-9.520672123247374-7.4792843670516485-6.792596923480321nannannanFalseFalseTrue21180False140False
aacccdpt2021:001725846469.184-6.520000.0nan-11.056245070136672-9.227175305920179-8.611526489053057nannannanFalseFalseTrue21180False145False
aacccdpt2021:001725846469.184-6.520000.0nan-12.571162710864538-10.969741242820325-10.430377873464037nannannanFalseFalseTrue21180False150False
aacccdpt2021:001725846469.184-6.520000.0nan-14.605129665817923-13.343158190521978-12.915498733847313nannannanFalseFalseTrue21180False155False
aacccdpt2021:001725846469.184-6.520000.0nan-16.57557266738169-15.694229156483809-15.400266370757095nannannanFalseFalseTrue21180False160False
aacccdpt2021:001725846469.184-6.520000.0nan-18.387547516394772-17.81370171909465-17.648499750591704nannannanFalseFalseTrue21180False165False
aacccdpt2021:001725846469.184-6.520000.0nan-19.926845471632735-19.582578916282927-19.48042740560264nannannanFalseFalseTrue21180False170False
aacccdpt2021:001725846469.184-6.520000.0nan-21.068476515559613-20.898499819555653-20.848218414810145nannannanFalseFalseTrue21180False175False
aacccdpt2021:001725846469.184-6.520000.0nan-22.21811822994504-22.21442072282838-22.207465924798406nannannanFalseFalseTrue21180False180False

Now we can start exploring this data. We won’t plot all the data we just generated, however the user can use the methods shown to further explore this dataset.

[6]:
filter_set = [
    {
        "pitch1": 170,
    },
]
plot_data = generate_timbre_dwell_plot_data(results, filter_set)
plot_dict = generate_timber_output_plot_dict(
    plot_data, "Dwell Time Yielded by 20Ks at 170 Degrees Pitch",
    legend=False
)
pio.show(plot_dict)
608010012014016018050k100k150k
Dwell Time Yielded by 20Ks at 170 Degrees PitchPitch (Degrees)Dwell Time (s)

Now compare that to the cold time necessary to balance a 20000s hot dwell at 90 degrees pitch.

[7]:
filter_set = [
    {
        "pitch1": 90,
    },
]
plot_data = generate_timbre_dwell_plot_data(results, filter_set)
plot_dict = generate_timber_output_plot_dict(
    plot_data, "Dwell Time Required to Balance 20Ks at 90 Degrees Pitch",
    legend=False
)
pio.show(plot_dict)
6080100120140160180020k40k60k80k100k120k
Dwell Time Required to Balance 20Ks at 90 Degrees PitchPitch (Degrees)Dwell Time (s)

We could combine these data into a single plot, however as these data are not directly comparable, displaying these two results together can result in more confusion than clarity. For example, 20ks of dwell time at 170 degrees pitch on 2021:001 will yield approximately 38.6Ks of available hot time at 90 degrees pitch (shown on the red line), and 20Ks of hot time at 90 degrees pitch requires approximately 9.750Ks of cold time to balance this 20Ks hot dwell (shown on the blue line), therefore the cooling (blue) data does not represent the time necessary to balance the shown heating (red) dwells.

The next section describes how to produce heating and cooling dwell times that do balance each other, and can be intuitively displayed on the same chart.

Using Timbre to Plot Balanced Heating and Cooling Time (New Algorithm)

Instead of plotting heating and cooling durations for a fixed initial dwell time, which result in heating and cooling (red and blue) curves that are not directly comparable, we anchor a plot at a single heating condition and dwell time and use this as a starting point to build a set of heating and cooling curves that are directly comparable.

These are the basic steps that we’ll follow to produce a single balanced dwell time plot.

  1. Pick a representative hot attitude along with a hot dwell time, for this example we use the ACA model, with an initial dwell at 90 degrees for 40Ks.

  2. Determine the cooling time to support this duration at all attitudes (we will only plot converged results).

  3. From the converged results generated in step 2, pick a representative cooling attitude and note the calculated cooling time. For this example we use 170 degrees, with the dwell time calculated in step 2 for this pitch.

  4. Determine the hot time yielded by this cold dwell, this should include the 40Ks at 90 degrees pitch as a result (or very close), along with the proportional heating time at other pitch values.

  5. As a sanity check, back calculate the associated cooling times using each pitch + dwell 2 time output from step 4.

  6. Repeat steps 1-4 (step 5 can be optional) for other initial hot dwell times.

First, as before, we load the model and define the baseline conditions

[8]:
msid = "aacccdpt"
limit = -6.5
date = "2021:001:00:00:00"
aca_model_spec, aca_md5 = get_local_model("../timbre/tests/data/aca_spec.json")
model_init = {"aacccdpt": -6.5, "aca0": -6.5, "eclipse": False}

Next, we pick a representative hot dwell and an associated initial dwell time that we want to balance. The only rule in picking this representative hot dwell is that it is not near a “transition region” so that the balance time can be reliably calculated. I picked 90 degrees pitch as it is far enough from a transition region, and is often considered one of the hottest attitudes for this location (ACA).

A hot dwell of 40Ks is chosen as the balance hot dwell time since this is close to the maximum hot time that is commonly calculated. There is no rule that defines how to pick this specific dwell time, and one may want to re-run this analysis with different initial dwell times to expand the characterization of this model.

A range of destination pitch values is chosen. The entire pitch range is used rather than those that are known to be cooling pitch values, as it is best not to assume which attitudes cool or heat.

[9]:
phot = 90
thot = 40000
state_pairs = [({"pitch": phot}, {"pitch": p2}) for p2 in range(45, 181, 5)]
results1 = run_state_pairs(
    msid, aca_model_spec, model_init, limit, date, thot, state_pairs,
    limit_type="max"
)
Running simulations for state pair #: 1 out of 28.0

The next plot shows the dwell time, at various cooling attitudes, required to balance the 40Ks hot dwell at 90 degrees pitch. It is very important to recognize that all the cooling data shown below are equivalent in balancing the chosen hot dwell parameters.

[10]:
filter_set1 = [
    {
        "pitch1": phot,
    },
]
plot_data1 = generate_timbre_dwell_plot_data(
    results1,
    filter_set1,
    label_override="Cooling Dwells Calculated to Support 40Ks at 90 Degrees Pitch",
)
plot_dict = generate_timber_output_plot_dict(
    plot_data1, "Dwell Time Required to Balance 40Ks at 90 Degrees Pitch",
    legend=False
)
pio.show(plot_dict)
608010012014016018020k40k60k80k100k
Dwell Time Required to Balance 40Ks at 90 Degrees PitchPitch (Degrees)Dwell Time (s)

After determining the various cooling times that balance the chosen hot dwell parameters, a representative cooling attitude is chosen from the data shown above. As in Step 1, we want a value that is not near a transition region. I chose 170 degrees as this is also a commonly recognized cooling attitude for the ACA. A cooling time of 20.906Ks at 170 degrees pitch is calculated to balance the original 40Ks hot dwell at 90 degrees pitch (as shown above). This dwell time and attitude now serve as the initial dwell parameters used to calculate the available heating time at all heating attitudes (not just at 90 degrees).

[11]:
pcool = 170
tcool_ind = results1["pitch2"] == pcool
tcool = results1["t_dwell2"][tcool_ind].item()
state_pairs = [({"pitch": pcool}, {"pitch": p2}) for p2 in range(45, 181, 5)]
results2 = run_state_pairs(
    msid, aca_model_spec, model_init, limit, date, tcool, state_pairs,
    limit_type="max"
)
Running simulations for state pair #: 1 out of 28.0

The next plot shows the dwell time, at various heating attitudes, required to balance the 20.906Ks cold dwell at 107 degrees pitch (calculated above). It is very important to recognize that all the heating data shown below are equivalent in balancing the chosen cooling dwell parameters.

[12]:
filter_set2 = [
    {
        "pitch1": pcool,
    },
]
plot_data2 = generate_timbre_dwell_plot_data(
    results2,
    filter_set2,
    label_override="Heating Dwell Time Yielded by 20.906Ks at 170 Degrees Pitch",
)
plot_dict = generate_timber_output_plot_dict(
    plot_data2,
    f"Dwell Time Required to Balance {tcool:.0f}s at {pcool} Degrees Pitch",
    legend=False,
)
pio.show(plot_dict)
608010012014016018050k100k150k
Dwell Time Required to Balance 20526s at 170 Degrees PitchPitch (Degrees)Dwell Time (s)

At this point, the two charts above can be combined into the same plot to intuitiviely display balanced dwell information, however first it would be useful to include a sanity check to verify that these curves (red and blue) are indeed comparable and result in a balanced set of dwells. To perform this sanity check, each hot attitude and associated heating time shown in the plot above is used to recalculate the associated cooling time required to balance each hot dwell. We would expect that, since all heating data displayed above are equivalent in their ability to balance the cooling dwell chosen to produce the above plot, and since all the cooling data shown above in the prior plot are equivalent in their ability to balance the original heating dwell chosen in Step 1, that this recalculation of the cooling data would result in identical cooling data to those shown above in Step 1.

[13]:
hot_ind = results2["t_dwell2"][results2["converged"]] < 100000
results3 = np.array([], dtype=results.dtype)
for result in results2[results2["converged"]][hot_ind]:
    state_pairs = list(
        ({"pitch": result["pitch2"]}, {"pitch": p2}) for p2 in range(45, 181, 5)
    )
    t = result["t_dwell2"]
    rnew = run_state_pairs(
        msid, aca_model_spec, model_init, limit, date, t, state_pairs,
        limit_type="max"
    )
    results3 = np.hstack((results3, rnew))
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0
Running simulations for state pair #: 1 out of 28.0

The data above are now shown below in the same chart, listed as the first two entries in the plot legend. The recalculated cooling data are shown simply as a cloud of blue dots, without connecting lines. As expected, the recalculated cooling data is almost identical to the original cooling curve, with the exception of the values near the transition regions, where this location neither heats or cools quickly. This scatter in the data near the transition regions is not surprising, as these regions are both difficult to accurately predict in nominal schedules and difficult to characterize by Timbre and Pyger.

[14]:
plot_data2[0]["name"] = "Heating Dwell Time Yielded by All Cooling Dwells Shown"
filter_set3 = [
    {
        "colder_state": 2,
    },
]
plot_data3 = generate_timbre_dwell_plot_data(
    results3,
    filter_set3,
    lines=False,
    label_override="Recalculated Cooling to Support All Heating Dwells Shown",
)
plot_dict = generate_timber_output_plot_dict(
    plot_data1 + plot_data2 + plot_data3,
    f"Balanced Dwell Time (Anchored by 90 Degree Pitch Dwell at 40Ks)",
    legend=True,
    legend_dict=legend_format_top_left,
)
pio.show(plot_dict)
608010012014016018050k100k150k
Cooling Dwells Calculated to Support 40Ks at 90 Degrees PitchHeating Dwell Time Yielded by All Cooling Dwells ShownRecalculated Cooling to Support All Heating Dwells ShownBalanced Dwell Time (Anchored by 90 Degree Pitch Dwell at 40Ks)Pitch (Degrees)Dwell Time (s)

We can take this analysis one step further, by assembling a balanced schedule made solely of pitch and dwell times used to generate the plot above. The ideal output of such a schedule would be a temperature profile where the peaks all touched the limit without exceeding it. Unfortunately, as we can see in the “Converged Timbre Dwell Simulation” plot above, there is some noise or uncertainty in the output of Timbre, however the most important output is a schedule that does not “trend” hotter or colder over many dwells (outside of seasonal effects). If a schedule did trend hotter or colder, that would indicate, in aggregate, that the balance times calculated by Timbre are not balanced.

First import a helpful method from Timbre and define a couple more helpful methods to use the cases generated above.

[15]:
def get_random_dwell(cases):
    ind = np.random.randint(0, high=(len(cases) - 1))
    return cases[ind]


def get_random_schedule(hot_cases, cold_cases):
    times = []
    pitch = []
    t = -1
    for n in range(40):
        hot = get_random_dwell(hot_cases)
        cold = get_random_dwell(cold_cases)
        times.extend([0, cold["t_dwell2"], 0, hot["t_dwell2"]])
        pitch.extend([cold["pitch2"], cold["pitch2"], hot["pitch2"], hot["pitch2"]])
    schedule = {"pitch": np.array(pitch)}
    times = CxoTime(date).secs - 30 * 24 * 3600.0 + np.cumsum(times)
    return times, schedule

Next, filter the hot and cold balance dwell information from above to include only the converged solutions (which are the only ones plotted above).

[16]:
cold_cases = results1[(results1["converged"] == True)]
hot_cases = results2[(results2["converged"] == True)]

Combine the hot and cold dwells in alternating cold-hot-cold-hot… schedule.

[17]:
times, schedule = get_random_schedule(hot_cases, cold_cases)

Run this schedule and plot the output.

[18]:
model_init = {"aacccdpt": -6.5, "aca0": -5.0, "eclipse": False}
output = run_profile(times, schedule, msid, aca_model_spec, model_init)
plot_dict = generate_random_balanced_dwell_plot_dict(
    output[msid], 'Extended Random "Balanced" Schedule', units="Celsius"
)
pio.show(plot_dict)
Dec 62020Dec 20Jan 32021Jan 1750100150−8−7
Extended Random "Balanced" SchedulePitchTemperature (Celsius)Limit = -6.5C

As can be seen in the plot above, the temperature peaks do not all touch the limit line without exceeding it. The peaks bounce around both above and below the limit, however no clear increasing or decreasing temperature trend can be seen in the data. Note that each time the code above is run, a different schedule will be produced so it will be difficult to precisely reproduce the random balanced schedule plot above.

Generate a Composite Maximum Dwell Estimate

Manual calculation using MUPS2A, ACA, ACIS FP

Next well review how one can determine the composite maximum dwell capability over the observable pitch range. Instead of using all models, in this case we’ll use three models to simplify the process. Since the MUPS2A, ACA, and ACIS FP models tend to be the most limiting in their respective heating regions, we’ll use these three to define this composite dwell capability curve. A more comprehensive analysis using all models with associated dwell limits will be performed later, using built-in timbre methods.


Start of Iteration #1

ACA Model

First we start the iteration using the ACA model since that is what we are already familiar with.

[19]:
msid = "aacccdpt"
aca_model_spec, aca_md5 = get_local_model("../timbre/tests/data/aca_spec.json")
date = "2022:001:00:00:00"
aca_limit = -6.5

The constant conditions are conditions that do not change for the case we are running, such as roll, CCD count, FEP count, clocking, etc. In this case there are no constant conditions that need to be specified because the ACA model does not include roll, so we assign an empty list to this variable.

[20]:
aca_constant_conditions = {}

When the method of how to generate a set of balanced limited and offset dwell curve could be generated was introduced above for the ACA model, the concept of using “anchor points” was used to aid in defining these curves. It is important to remember that, for a set of balanced limited and offset curves, any point on a limited curve can be balanced by any point on the associated offset curve, and visa versa. This allows us to limit the number of pitch values we need to characterize a model’s dwell capability.

Since the ACA model is limited at normal sun, and 90 degrees reliably sits in that range, we’ll use 90 degrees to “anchor” the limited curve for the ACA model. Since the ACA model reliably cools at 160 degrees pitch, we’ll use that pitch to “anchor” the offset curve.

[21]:
aca_anchor_offset_pitch = 160
aca_anchor_limited_pitch = 90

To start we’ll assume the available offset time will be 100 Ks. This clearly is far higher than we would expect to be available, however this allows the algorithm to avoid being unnecessarily limited from the outset. As we step through this algorithm, this value will be updated with more realistic values.

Additionally, we’ll work with a very coarse distribution of pitch values, in fact we’ll only need four pitch values. These four pitch values either serve as an anchor limited pitch or anchor offset pitch for the three models we’ll be using for this example.

[22]:
aca_anchor_offset_time = 100000
pitch_range = [60, 90, 160, 170]

Initialize a class that includes the methods necessary to balance the ACA model.

[23]:
aca = BalanceAACCCDPT(date, aca_model_spec, aca_limit, aca_constant_conditions)

Find the limited time at the aca_anchor_limited_pitch that balances the previously defined aca_anchor_offset_time at the anchor_offset_pitch. This will yield the last piece of information we’ll need to resolve the full limited and offset curves. Please keep in mind that, since the aca_anchor_offset_time was set to a large value, we can expect a similarly unrealistic value for the aca_anchor_limited_time, however this value will be refined during subsequent iterations to a realistic value.

[24]:
aca.find_anchor_condition(
    aca_anchor_offset_pitch, aca_anchor_limited_pitch, aca_anchor_offset_time,
    aca_limit
)
print(f"Anchor Limited Time: {aca.anchor_limited_time}")
Anchor Limited Time: 72203.80963871328

Now that the anchor time has been defined, resolve the limited and offset curves for only the pitch values we need. The generate_balanced_pitch_dwells method executes the following two steps and then combines the data into the results attribute. 1. Calculate the offset dwell curve for all values in pitch_range yielded by aca_anchor_limited_time at the aca_anchor_limited_pitch. This should reproduce an offset time at aca_anchor_offset_pitch that is very close , but not necessarily exactly the same as the original aca_anchor_offset_time assumed. In row 2 below, you see that at the aca_anchor_offset_pitch of 160 degrees, a value of 104406 seconds was calculated, not 100000 seconds. This is expected and is an example of slight errors that can creep into these calculations. 2. Use the newly calculated offset dwell time at the aca_anchor_offset_pitch of 160 degrees to calculate the limited dwell curve for all pitch values defined in pitch_range. This will also likely produce a new anchor limited time at the aca_anchor_limited_pitch of 90 degrees that is slightly different than that calculated above.

[25]:
aca.results = aca.generate_balanced_pitch_dwells(
    aca.datesecs,
    aca_anchor_limited_pitch,
    aca.anchor_limited_time,
    aca_anchor_offset_pitch,
    aca_anchor_offset_time,
    aca.limit,
    pitch_range,
)

The set of results that are produced include both the limited and offset conditions, along with the cases that did not “converge”.

[26]:
pd.DataFrame(aca.results)
[26]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... max_pseudo converged unconverged_hot unconverged_cold hotter_state colder_state pitch1 eclipse1 pitch2 eclipse2
0 aacccdpt 2022:001 7.573825e+08 -6.5 72203.809639 NaN -6.096723 -5.397631 -2.984467 NaN ... NaN False True False 1 2 90 False 60 False
1 aacccdpt 2022:001 7.573825e+08 -6.5 72203.809639 NaN 0.656682 0.723773 0.774326 NaN ... NaN False True False 1 2 90 False 90 False
2 aacccdpt 2022:001 7.573825e+08 -6.5 72203.809639 100448.041122 -11.304270 -8.887973 -6.500000 NaN ... NaN True False False 1 2 90 False 160 False
3 aacccdpt 2022:001 7.573825e+08 -6.5 72203.809639 48696.835196 -10.281769 -8.444516 -6.500000 NaN ... NaN True False False 1 2 90 False 170 False
4 aacccdpt 2022:001 7.573825e+08 -6.5 100448.041122 269180.645364 -11.921017 -8.681753 -6.500000 NaN ... NaN True False False 2 1 160 False 60 False
5 aacccdpt 2022:001 7.573825e+08 -6.5 100448.041122 72327.365135 -11.286833 -8.879340 -6.500000 NaN ... NaN True False False 2 1 160 False 90 False
6 aacccdpt 2022:001 7.573825e+08 -6.5 100448.041122 NaN -15.832137 -15.788105 -15.758273 NaN ... NaN False False True 2 1 160 False 160 False
7 aacccdpt 2022:001 7.573825e+08 -6.5 100448.041122 NaN -15.835247 -15.790552 -15.759290 NaN ... NaN False False True 1 2 160 False 170 False

8 rows × 21 columns

It will be helpful to keep the offset dwell results and the limited dwell results separate and organized by pitch. Timbre includes methods to extract the limited data using the original anchor_offset_pitch, and likewise a method to extract the offset data using the original anchor_limited_pitch.

[27]:
aca_limited_results = get_limited_results(aca.results, aca_anchor_offset_pitch)
pd.DataFrame(aca_limited_results)
[27]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... max_pseudo converged unconverged_hot unconverged_cold hotter_state colder_state pitch1 eclipse1 pitch2 eclipse2
0 aacccdpt 2022:001 7.573825e+08 -6.5 100448.041122 269180.645364 -11.921017 -8.681753 -6.5 NaN ... NaN True False False 2 1 160 False 60 False
1 aacccdpt 2022:001 7.573825e+08 -6.5 100448.041122 72327.365135 -11.286833 -8.879340 -6.5 NaN ... NaN True False False 2 1 160 False 90 False

2 rows × 21 columns

[28]:
aca_offset_results = get_offset_results(aca.results, aca_anchor_limited_pitch)
pd.DataFrame(aca_offset_results)
[28]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... max_pseudo converged unconverged_hot unconverged_cold hotter_state colder_state pitch1 eclipse1 pitch2 eclipse2
0 aacccdpt 2022:001 7.573825e+08 -6.5 72203.809639 100448.041122 -11.304270 -8.887973 -6.5 NaN ... NaN True False False 1 2 90 False 160 False
1 aacccdpt 2022:001 7.573825e+08 -6.5 72203.809639 48696.835196 -10.281769 -8.444516 -6.5 NaN ... NaN True False False 1 2 90 False 170 False

2 rows × 21 columns

It will also be helpful to keep a record of max dwell durations, as this will be key to determining the maximum available cooling time. We want the minimum of the set of values including the existing maximum dwell duration of 100 Ks, and the ACA dwell limits. Since a value over 100 Ks was calculated for the ACA at 60 degrees, the value of 100 Ks remains.

[29]:
dwell_limits = pd.Series([100000, 100000, 100000, 100000], index=[60, 90, 160, 170])

pitch = aca_limited_results["pitch2"]
t_dwell2 = aca_limited_results["t_dwell2"]
dwell_limits.loc[pitch] = np.minimum(t_dwell2, dwell_limits.loc[pitch])
dwell_limits
[29]:
60     100000.000000
90      72327.365135
160    100000.000000
170    100000.000000
dtype: float64

These values will serve as the maximum available offset time for further steps and iterations.

ACIS FP Model

Next, determine the maximum available limited time, and the associated offset time for the ACIS FP model.

[30]:
msid = "fptemp_11"
fp_model_spec, fp_md5 = get_local_model("../timbre/tests/data/acisfp_spec.json")
fp_limit = -112.0
roll = 0.0
chips = 4
fp_constant_conditions = {
    "roll": roll,
    "fep_count": chips,
    "ccd_count": chips,
    "clocking": True,
    "vid_board": True,
    "sim_z": 100000,
}
fp_anchor_offset_pitch = 60
fp_anchor_limited_pitch = 170

The anchor offset time will be taken from the dwell_limits data at a pitch of 60 degrees, though at this point it is still set to 100 Ks.

[31]:
fp_anchor_offset_time = dwell_limits.loc[fp_anchor_offset_pitch]
[32]:
fp = BalanceFPTEMP_11(date, fp_model_spec, fp_limit, fp_constant_conditions)
fp.find_anchor_condition(
    fp_anchor_offset_pitch,
    fp_anchor_limited_pitch,
    fp_anchor_offset_time,
    fp_limit
)
print(f"Anchor Limited Time: {fp.anchor_limited_time}")
Anchor Limited Time: 24670.105416242033
[33]:
fp.results = fp.generate_balanced_pitch_dwells(
    fp.datesecs,
    fp_anchor_limited_pitch,
    fp.anchor_limited_time,
    fp_anchor_offset_pitch,
    fp_anchor_offset_time,
    fp.limit,
    pitch_range,
)
pd.DataFrame(fp.results)
[33]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... eclipse2 dpa_power2 orbitephem0_x2 orbitephem0_y2 orbitephem0_z2 aoattqt12 aoattqt22 aoattqt32 aoattqt42 dh_heater2
0 fptemp 2022:001 7.573825e+08 -112.0 24670.105416 39398.209103 -120.033283 -117.013528 -112.000000 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
1 fptemp 2022:001 7.573825e+08 -112.0 24670.105416 NaN -119.873395 -119.443016 -111.600568 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
2 fptemp 2022:001 7.573825e+08 -112.0 24670.105416 NaN -100.646472 -100.545703 -100.476067 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
3 fptemp 2022:001 7.573825e+08 -112.0 24670.105416 NaN -100.682247 -100.569438 -100.487636 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
4 fptemp 2022:001 7.573825e+08 -112.0 39398.209103 NaN -123.078797 -123.040900 -122.991956 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
5 fptemp 2022:001 7.573825e+08 -112.0 39398.209103 NaN -122.129791 -119.834898 -119.615903 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
6 fptemp 2022:001 7.573825e+08 -112.0 39398.209103 24601.070822 -119.937834 -117.008236 -112.000000 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
7 fptemp 2022:001 7.573825e+08 -112.0 39398.209103 24695.416134 -120.018033 -117.012391 -112.000000 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False

8 rows × 51 columns

[34]:
fp_limited_results = get_limited_results(fp.results, fp_anchor_offset_pitch)
pd.DataFrame(fp_limited_results)
[34]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... eclipse2 dpa_power2 orbitephem0_x2 orbitephem0_y2 orbitephem0_z2 aoattqt12 aoattqt22 aoattqt32 aoattqt42 dh_heater2
0 fptemp 2022:001 7.573825e+08 -112.0 39398.209103 24601.070822 -119.937834 -117.008236 -112.0 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False
1 fptemp 2022:001 7.573825e+08 -112.0 39398.209103 24695.416134 -120.018033 -117.012391 -112.0 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False

2 rows × 51 columns

[35]:
fp_offset_results = get_offset_results(fp.results, fp_anchor_limited_pitch)
pd.DataFrame(fp_offset_results)
[35]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... eclipse2 dpa_power2 orbitephem0_x2 orbitephem0_y2 orbitephem0_z2 aoattqt12 aoattqt22 aoattqt32 aoattqt42 dh_heater2
0 fptemp 2022:001 7.573825e+08 -112.0 24670.105416 39398.209103 -120.033283 -117.013528 -112.0 NaN ... False 0.0 25000000.0 25000000.0 25000000.0 0.0 0.0 0.0 1.0 False

1 rows × 51 columns

[36]:
pitch = fp_limited_results["pitch2"]
t_dwell2 = fp_limited_results["t_dwell2"]
dwell_limits.loc[pitch] = np.minimum(t_dwell2, dwell_limits.loc[pitch])
dwell_limits
[36]:
60     100000.000000
90      72327.365135
160     24601.070822
170     24695.416134
dtype: float64

MUPS2A Model

[37]:
msid = "pm2thv1t"
mups2a_model_spec, mups2a_md5 = get_local_model(
    "../timbre/tests/data/pm2thv1t_spec.json"
)
mups2a_limit = f_to_c(210.0)
mups2a_constant_conditions = {"roll": roll}
mups2a_anchor_offset_pitch = 160
mups2a_anchor_limited_pitch = 60
mups2a_anchor_offset_time = dwell_limits.loc[mups2a_anchor_offset_pitch]
mups2a = BalancePM2THV1T(
    date, mups2a_model_spec, mups2a_limit, mups2a_constant_conditions
)
mups2a.find_anchor_condition(
    mups2a_anchor_offset_pitch,
    mups2a_anchor_limited_pitch,
    mups2a_anchor_offset_time,
    mups2a_limit,
)
print(f"Anchor Limited Time: {mups2a.anchor_limited_time}")
Anchor Limited Time: 18385.409767875644
[38]:
mups2a.results = mups2a.generate_balanced_pitch_dwells(
    mups2a.datesecs,
    mups2a_anchor_limited_pitch,
    mups2a.anchor_limited_time,
    mups2a_anchor_offset_pitch,
    mups2a_anchor_offset_time,
    mups2a.limit,
    pitch_range,
)
pd.DataFrame(mups2a.results)
[38]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... unconverged_hot unconverged_cold hotter_state colder_state pitch1 roll1 eclipse1 pitch2 roll2 eclipse2
0 pm2thv1t 2022:001 7.573825e+08 98.888889 18385.409768 NaN 109.493364 109.625270 109.728760 NaN ... True False 1 2 60 0.0 False 60 0.0 False
1 pm2thv1t 2022:001 7.573825e+08 98.888889 18385.409768 NaN 101.362551 101.647938 108.138645 NaN ... True False 1 2 60 0.0 False 90 0.0 False
2 pm2thv1t 2022:001 7.573825e+08 98.888889 18385.409768 25270.392514 49.949234 71.277158 98.888889 NaN ... False False 1 2 60 0.0 False 160 0.0 False
3 pm2thv1t 2022:001 7.573825e+08 98.888889 18385.409768 10902.108270 44.506473 75.980413 98.888889 NaN ... False False 1 2 60 0.0 False 170 0.0 False
4 pm2thv1t 2022:001 7.573825e+08 98.888889 25270.392514 18512.578263 50.002154 71.641495 98.888889 NaN ... False False 2 1 160 0.0 False 60 0.0 False
5 pm2thv1t 2022:001 7.573825e+08 98.888889 25270.392514 49587.952066 51.947885 82.013789 98.888889 NaN ... False False 2 1 160 0.0 False 90 0.0 False
6 pm2thv1t 2022:001 7.573825e+08 98.888889 25270.392514 NaN 43.544993 43.617014 43.660588 NaN ... False True 1 2 160 0.0 False 160 0.0 False
7 pm2thv1t 2022:001 7.573825e+08 98.888889 25270.392514 NaN 43.544993 43.617014 43.660588 NaN ... False True 1 2 160 0.0 False 170 0.0 False

8 rows × 23 columns

[39]:
mups2a_limited_results = get_limited_results(mups2a.results,
                                             mups2a_anchor_offset_pitch)
pd.DataFrame(mups2a_limited_results)
[39]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... unconverged_hot unconverged_cold hotter_state colder_state pitch1 roll1 eclipse1 pitch2 roll2 eclipse2
0 pm2thv1t 2022:001 7.573825e+08 98.888889 25270.392514 18512.578263 50.002154 71.641495 98.888889 NaN ... False False 2 1 160 0.0 False 60 0.0 False
1 pm2thv1t 2022:001 7.573825e+08 98.888889 25270.392514 49587.952066 51.947885 82.013789 98.888889 NaN ... False False 2 1 160 0.0 False 90 0.0 False

2 rows × 23 columns

[40]:
mups2a_offset_results = get_offset_results(mups2a.results,
                                           mups2a_anchor_limited_pitch)
pd.DataFrame(mups2a_offset_results)
[40]:
msid date datesecs limit t_dwell1 t_dwell2 min_temp mean_temp max_temp min_pseudo ... unconverged_hot unconverged_cold hotter_state colder_state pitch1 roll1 eclipse1 pitch2 roll2 eclipse2
0 pm2thv1t 2022:001 7.573825e+08 98.888889 18385.409768 25270.392514 49.949234 71.277158 98.888889 NaN ... False False 1 2 60 0.0 False 160 0.0 False
1 pm2thv1t 2022:001 7.573825e+08 98.888889 18385.409768 10902.108270 44.506473 75.980413 98.888889 NaN ... False False 1 2 60 0.0 False 170 0.0 False

2 rows × 23 columns

[41]:
pitch = mups2a_limited_results["pitch2"]
t_dwell2 = mups2a_limited_results["t_dwell2"]
dwell_limits.loc[pitch] = np.minimum(t_dwell2, dwell_limits.loc[pitch])
dwell_limits
[41]:
60     18512.578263
90     49587.952066
160    24601.070822
170    24695.416134
dtype: float64

Iteration #1 Results

As we can see below, the originally assumed offset dwell time of 100 Ks for the ACA and ACIS FP steps exceeds the actual available offset times. This likely resulted in the calculated limited dwell time exceeding the actual achievable limited dwell time, so another iteration will be necessary. This next iteration will start with the final available dwell times calculated above, stored in the dwell_limits datastructure.

[42]:
dwell_assumptions = (
    (
        aca_anchor_limited_pitch,
        aca.anchor_limited_time,
        aca_anchor_offset_pitch,
        aca.anchor_offset_time,
        dwell_limits.loc[aca_anchor_offset_pitch],
    ),
    (
        fp_anchor_limited_pitch,
        fp.anchor_limited_time,
        fp_anchor_offset_pitch,
        fp.anchor_offset_time,
        dwell_limits.loc[fp_anchor_offset_pitch],
    ),
    (
        mups2a_anchor_limited_pitch,
        mups2a.anchor_limited_time,
        mups2a_anchor_offset_pitch,
        mups2a.anchor_offset_time,
        dwell_limits.loc[mups2a_anchor_offset_pitch],
    ),
)
pd.DataFrame(
    dwell_assumptions,
    index=("ACA", "ACIS FP", "MUPS2A"),
    columns=(
        "Anchor Limited Pitch",
        "Calculated Anchor Limited Time",
        "Anchor Offset Pitch",
        "Originally Assumed Available Offset Time",
        "Final Available Dwell Time",
    ),
)
[42]:
Anchor Limited Pitch Calculated Anchor Limited Time Anchor Offset Pitch Originally Assumed Available Offset Time Final Available Dwell Time
ACA 90 72203.809639 160 100000.000000 24601.070822
ACIS FP 170 24670.105416 60 100000.000000 18512.578263
MUPS2A 60 18385.409768 160 24601.070822 24601.070822

Start of Iteration #2

[43]:
aca_anchor_offset_time = dwell_limits.loc[aca_anchor_offset_pitch]
aca.find_anchor_condition(
    aca_anchor_offset_pitch,
    aca_anchor_limited_pitch,
    aca_anchor_offset_time,
    aca_limit
)
aca.results = aca.generate_balanced_pitch_dwells(
    aca.datesecs,
    aca_anchor_limited_pitch,
    aca.anchor_limited_time,
    aca_anchor_offset_pitch,
    aca_anchor_offset_time,
    aca.limit,
    pitch_range,
)
aca_limited_results = get_limited_results(aca.results, aca_anchor_offset_pitch)
aca_offset_results = get_offset_results(aca.results, aca_anchor_limited_pitch)

dwell_limits.loc[aca_limited_results["pitch2"]] = np.minimum(
    aca_limited_results["t_dwell2"],
    dwell_limits.loc[aca_limited_results["pitch2"]]
)
dwell_limits
[43]:
60     18512.578263
90     28900.515806
160    24601.070822
170    24695.416134
dtype: float64
[44]:
fp_anchor_offset_time = dwell_limits.loc[fp_anchor_offset_pitch]
fp.find_anchor_condition(
    fp_anchor_offset_pitch,
    fp_anchor_limited_pitch,
    fp_anchor_offset_time,
    fp_limit
)
fp.results = fp.generate_balanced_pitch_dwells(
    fp.datesecs,
    fp_anchor_limited_pitch,
    fp.anchor_limited_time,
    fp_anchor_offset_pitch,
    fp_anchor_offset_time,
    fp.limit,
    pitch_range,
)
fp_limited_results = get_limited_results(fp.results, fp_anchor_offset_pitch)
fp_offset_results = get_offset_results(fp.results, fp_anchor_limited_pitch)

dwell_limits.loc[fp_limited_results["pitch2"]] = np.minimum(
    fp_limited_results["t_dwell2"], dwell_limits.loc[fp_limited_results["pitch2"]]
)
dwell_limits
[44]:
60     18512.578263
90     28900.515806
160    18917.180033
170    19380.289094
dtype: float64
[45]:
mups2a_anchor_offset_time = dwell_limits.loc[mups2a_anchor_offset_pitch]
mups2a.find_anchor_condition(
    mups2a_anchor_offset_pitch,
    mups2a_anchor_limited_pitch,
    mups2a_anchor_offset_time,
    mups2a_limit,
)
mups2a.results = mups2a.generate_balanced_pitch_dwells(
    mups2a.datesecs,
    mups2a_anchor_limited_pitch,
    mups2a.anchor_limited_time,
    mups2a_anchor_offset_pitch,
    mups2a_anchor_offset_time,
    mups2a.limit,
    pitch_range,
)
mups2a_limited_results = get_limited_results(mups2a.results,
                                             mups2a_anchor_offset_pitch)
mups2a_offset_results = get_offset_results(mups2a.results,
                                           mups2a_anchor_limited_pitch)

dwell_limits.loc[mups2a_limited_results["pitch2"]] = np.minimum(
    mups2a_limited_results["t_dwell2"],
    dwell_limits.loc[mups2a_limited_results["pitch2"]],
)
dwell_limits
[45]:
60     17021.054085
90     28900.515806
160    18917.180033
170    19380.289094
dtype: float64
[46]:
dwell_assumptions = (
    (
        aca_anchor_limited_pitch,
        aca.anchor_limited_time,
        aca_anchor_offset_pitch,
        aca.anchor_offset_time,
        dwell_limits.loc[aca_anchor_offset_pitch],
    ),
    (
        fp_anchor_limited_pitch,
        fp.anchor_limited_time,
        fp_anchor_offset_pitch,
        fp.anchor_offset_time,
        dwell_limits.loc[fp_anchor_offset_pitch],
    ),
    (
        mups2a_anchor_limited_pitch,
        mups2a.anchor_limited_time,
        mups2a_anchor_offset_pitch,
        mups2a.anchor_offset_time,
        dwell_limits.loc[mups2a_anchor_offset_pitch],
    ),
)
pd.DataFrame(
    dwell_assumptions,
    index=("ACA", "ACIS FP", "MUPS2A"),
    columns=(
        "Anchor Limited Pitch",
        "Calculated Anchor Limited Time",
        "Anchor Offset Pitch",
        "Originally Assumed Available Offset Time",
        "Final Available Dwell Time",
    ),
)
[46]:
Anchor Limited Pitch Calculated Anchor Limited Time Anchor Offset Pitch Originally Assumed Available Offset Time Final Available Dwell Time
ACA 90 28942.027620 160 24601.070822 18917.180033
ACIS FP 170 19108.309075 60 18512.578263 17021.054085
MUPS2A 60 16999.747280 160 18917.180033 18917.180033

As expected, the anchor limited times (i.e. the max hot times) have decreased as available offset time has been reduced. The final available dwell times (available for offset time) were also reduced so another iteration is necessary.


Start of Iteration #3

[47]:
aca_anchor_offset_time = dwell_limits.loc[aca_anchor_offset_pitch]
aca.find_anchor_condition(
    aca_anchor_offset_pitch,
    aca_anchor_limited_pitch,
    aca_anchor_offset_time,
    aca_limit
)
aca.results = aca.generate_balanced_pitch_dwells(
    aca.datesecs,
    aca_anchor_limited_pitch,
    aca.anchor_limited_time,
    aca_anchor_offset_pitch,
    aca_anchor_offset_time,
    aca.limit,
    pitch_range,
)
aca_limited_results = get_limited_results(aca.results, aca_anchor_offset_pitch)
aca_offset_results = get_offset_results(aca.results, aca_anchor_limited_pitch)

dwell_limits.loc[aca_limited_results["pitch2"]] = np.minimum(
    aca_limited_results["t_dwell2"], dwell_limits.loc[aca_limited_results["pitch2"]]
)
dwell_limits
[47]:
60     17021.054085
90     22652.796803
160    18917.180033
170    19380.289094
dtype: float64
[48]:
fp_anchor_offset_time = dwell_limits.loc[fp_anchor_offset_pitch]
fp.find_anchor_condition(
    fp_anchor_offset_pitch,
    fp_anchor_limited_pitch,
    fp_anchor_offset_time,
    fp_limit
)
fp.results = fp.generate_balanced_pitch_dwells(
    fp.datesecs,
    fp_anchor_limited_pitch,
    fp.anchor_limited_time,
    fp_anchor_offset_pitch,
    fp_anchor_offset_time,
    fp.limit,
    pitch_range,
)
fp_limited_results = get_limited_results(fp.results, fp_anchor_offset_pitch)
fp_offset_results = get_offset_results(fp.results, fp_anchor_limited_pitch)

dwell_limits.loc[fp_limited_results["pitch2"]] = np.minimum(
    fp_limited_results["t_dwell2"], dwell_limits.loc[fp_limited_results["pitch2"]]
)
dwell_limits
[48]:
60     17021.054085
90     22652.796803
160    17371.675441
170    17706.780277
dtype: float64
[49]:
mups2a_anchor_offset_time = dwell_limits.loc[mups2a_anchor_offset_pitch]
mups2a.find_anchor_condition(
    mups2a_anchor_offset_pitch,
    mups2a_anchor_limited_pitch,
    mups2a_anchor_offset_time,
    mups2a_limit,
)
mups2a.results = mups2a.generate_balanced_pitch_dwells(
    mups2a.datesecs,
    mups2a_anchor_limited_pitch,
    mups2a.anchor_limited_time,
    mups2a_anchor_offset_pitch,
    mups2a_anchor_offset_time,
    mups2a.limit,
    pitch_range,
)
mups2a_limited_results = get_limited_results(mups2a.results,
                                             mups2a_anchor_offset_pitch)
mups2a_offset_results = get_offset_results(mups2a.results,
                                           mups2a_anchor_limited_pitch)

dwell_limits.loc[mups2a_limited_results["pitch2"]] = np.minimum(
    mups2a_limited_results["t_dwell2"],
    dwell_limits.loc[mups2a_limited_results["pitch2"]],
)
dwell_limits
[49]:
60     16576.192900
90     22652.796803
160    17371.675441
170    17706.780277
dtype: float64
[50]:
dwell_assumptions = (
    (
        aca_anchor_limited_pitch,
        aca.anchor_limited_time,
        aca_anchor_offset_pitch,
        aca.anchor_offset_time,
        dwell_limits.loc[aca_anchor_offset_pitch],
    ),
    (
        fp_anchor_limited_pitch,
        fp.anchor_limited_time,
        fp_anchor_offset_pitch,
        fp.anchor_offset_time,
        dwell_limits.loc[fp_anchor_offset_pitch],
    ),
    (
        mups2a_anchor_limited_pitch,
        mups2a.anchor_limited_time,
        mups2a_anchor_offset_pitch,
        mups2a.anchor_offset_time,
        dwell_limits.loc[mups2a_anchor_offset_pitch],
    ),
)
pd.DataFrame(
    dwell_assumptions,
    index=("ACA", "ACIS FP", "MUPS2A"),
    columns=(
        "Anchor Limited Pitch",
        "Calculated Anchor Limited Time",
        "Anchor Offset Pitch",
        "Originally Assumed Available Offset Time",
        "Final Available Dwell Time",
    ),
)
[50]:
Anchor Limited Pitch Calculated Anchor Limited Time Anchor Offset Pitch Originally Assumed Available Offset Time Final Available Dwell Time
ACA 90 22811.677902 160 18917.180033 17371.675441
ACIS FP 170 18037.256247 60 17021.054085 16576.192900
MUPS2A 60 16572.032038 160 17371.675441 17371.675441

The newly calculated anchor limited times decreased as a result of the further decreased offset times. The only model that used more offset time than available after running the rest of the models was the ACA model (comparing the two righthand columns).


Start of Iteration #4

[51]:
aca_anchor_offset_time = dwell_limits.loc[aca_anchor_offset_pitch]
aca.find_anchor_condition(
    aca_anchor_offset_pitch,
    aca_anchor_limited_pitch,
    aca_anchor_offset_time,
    aca_limit
)
aca.results = aca.generate_balanced_pitch_dwells(
    aca.datesecs,
    aca_anchor_limited_pitch,
    aca.anchor_limited_time,
    aca_anchor_offset_pitch,
    aca_anchor_offset_time,
    aca.limit,
    pitch_range,
)
aca_limited_results = get_limited_results(aca.results, aca_anchor_offset_pitch)
aca_offset_results = get_offset_results(aca.results, aca_anchor_limited_pitch)

dwell_limits.loc[aca_limited_results["pitch2"]] = np.minimum(
    aca_limited_results["t_dwell2"], dwell_limits.loc[aca_limited_results["pitch2"]]
)
dwell_limits
[51]:
60     16576.192900
90     21186.704458
160    17371.675441
170    17706.780277
dtype: float64
[52]:
fp_anchor_offset_time = dwell_limits.loc[fp_anchor_offset_pitch]
fp.find_anchor_condition(
    fp_anchor_offset_pitch,
    fp_anchor_limited_pitch,
    fp_anchor_offset_time,
    fp_limit
)
fp.results = fp.generate_balanced_pitch_dwells(
    fp.datesecs,
    fp_anchor_limited_pitch,
    fp.anchor_limited_time,
    fp_anchor_offset_pitch,
    fp_anchor_offset_time,
    fp.limit,
    pitch_range,
)
fp_limited_results = get_limited_results(fp.results, fp_anchor_offset_pitch)
fp_offset_results = get_offset_results(fp.results, fp_anchor_limited_pitch)

dwell_limits.loc[fp_limited_results["pitch2"]] = np.minimum(
    fp_limited_results["t_dwell2"], dwell_limits.loc[fp_limited_results["pitch2"]]
)
dwell_limits
[52]:
60     16576.192900
90     21186.704458
160    17371.675441
170    17675.429537
dtype: float64
[53]:
mups2a_anchor_offset_time = dwell_limits.loc[mups2a_anchor_offset_pitch]
mups2a.find_anchor_condition(
    mups2a_anchor_offset_pitch,
    mups2a_anchor_limited_pitch,
    mups2a_anchor_offset_time,
    mups2a_limit,
)
mups2a.results = mups2a.generate_balanced_pitch_dwells(
    mups2a.datesecs,
    mups2a_anchor_limited_pitch,
    mups2a.anchor_limited_time,
    mups2a_anchor_offset_pitch,
    mups2a_anchor_offset_time,
    mups2a.limit,
    pitch_range,
)
mups2a_limited_results = get_limited_results(mups2a.results,
                                             mups2a_anchor_offset_pitch)
mups2a_offset_results = get_offset_results(mups2a.results,
                                           mups2a_anchor_limited_pitch)

dwell_limits.loc[mups2a_limited_results["pitch2"]] = np.minimum(
    mups2a_limited_results["t_dwell2"],
    dwell_limits.loc[mups2a_limited_results["pitch2"]],
)
dwell_limits
[53]:
60     16576.192900
90     21186.704458
160    17371.675441
170    17675.429537
dtype: float64
[54]:
dwell_assumptions = (
    (
        aca_anchor_limited_pitch,
        aca.anchor_limited_time,
        aca_anchor_offset_pitch,
        aca.anchor_offset_time,
        dwell_limits.loc[aca_anchor_offset_pitch],
    ),
    (
        fp_anchor_limited_pitch,
        fp.anchor_limited_time,
        fp_anchor_offset_pitch,
        fp.anchor_offset_time,
        dwell_limits.loc[fp_anchor_offset_pitch],
    ),
    (
        mups2a_anchor_limited_pitch,
        mups2a.anchor_limited_time,
        mups2a_anchor_offset_pitch,
        mups2a.anchor_offset_time,
        dwell_limits.loc[mups2a_anchor_offset_pitch],
    ),
)
pd.DataFrame(
    dwell_assumptions,
    index=("ACA", "ACIS FP", "MUPS2A"),
    columns=(
        "Anchor Limited Pitch",
        "Calculated Anchor Limited Time",
        "Anchor Offset Pitch",
        "Originally Assumed Available Offset Time",
        "Final Available Dwell Time",
    ),
)
[54]:
Anchor Limited Pitch Calculated Anchor Limited Time Anchor Offset Pitch Originally Assumed Available Offset Time Final Available Dwell Time
ACA 90 21209.654645 160 17371.675441 17371.675441
ACIS FP 170 17954.144825 60 16576.192900 16576.192900
MUPS2A 60 16572.032038 160 17371.675441 17371.675441

Given the originally assumed offset times at the start of this iteration were not reduced, we now have a “balanced” dwell configuration and can now fill out the result of the pitch range.


Final Iteration

In order to calculate the dwell times for the full pitch range, all we need to do is redefine the pitch range and re-run the same methods we used earlier. The only caveate is that the original pitch values used above need to be included in this final pitch range.

[55]:
pitch_range = np.arange(45, 181, 5)
[56]:
aca_anchor_offset_time = dwell_limits.loc[aca_anchor_offset_pitch]
aca.find_anchor_condition(
    aca_anchor_offset_pitch,
    aca_anchor_limited_pitch,
    aca_anchor_offset_time,
    aca_limit
)
aca.results = aca.generate_balanced_pitch_dwells(
    aca.datesecs,
    aca_anchor_limited_pitch,
    aca.anchor_limited_time,
    aca_anchor_offset_pitch,
    aca_anchor_offset_time,
    aca.limit,
    pitch_range,
)
aca_limited_results = get_limited_results(aca.results, aca_anchor_offset_pitch)
aca_offset_results = get_offset_results(aca.results, aca_anchor_limited_pitch)
[57]:
fp_anchor_offset_time = dwell_limits.loc[fp_anchor_offset_pitch]
fp.find_anchor_condition(
    fp_anchor_offset_pitch,
    fp_anchor_limited_pitch,
    fp_anchor_offset_time,
    fp_limit
)
fp.results = fp.generate_balanced_pitch_dwells(
    fp.datesecs,
    fp_anchor_limited_pitch,
    fp.anchor_limited_time,
    fp_anchor_offset_pitch,
    fp_anchor_offset_time,
    fp.limit,
    pitch_range,
)
fp_limited_results = get_limited_results(fp.results, fp_anchor_offset_pitch)
fp_offset_results = get_offset_results(fp.results, fp_anchor_limited_pitch)
[58]:
mups2a_anchor_offset_time = dwell_limits.loc[mups2a_anchor_offset_pitch]
mups2a.find_anchor_condition(
    mups2a_anchor_offset_pitch,
    mups2a_anchor_limited_pitch,
    mups2a_anchor_offset_time,
    mups2a_limit,
)
mups2a.results = mups2a.generate_balanced_pitch_dwells(
    mups2a.datesecs,
    mups2a_anchor_limited_pitch,
    mups2a.anchor_limited_time,
    mups2a_anchor_offset_pitch,
    mups2a_anchor_offset_time,
    mups2a.limit,
    pitch_range,
)
mups2a_limited_results = get_limited_results(mups2a.results, mups2a_anchor_offset_pitch)
mups2a_offset_results = get_offset_results(mups2a.results, mups2a_anchor_limited_pitch)

Now that we have the data we need, organize the limited data and the offset data into some separate datastructures. This will make it easier to use this data.

[59]:
final_limited_results = pd.DataFrame(
    [], index=pitch_range, columns=("AACCCDPT", "FPTEMP_11", "PM2THV1T")
)
final_limited_results.loc[
    aca_limited_results["pitch2"], "AACCCDPT"
] = aca_limited_results["t_dwell2"]
final_limited_results.loc[
    fp_limited_results["pitch2"], "FPTEMP_11"
] = fp_limited_results["t_dwell2"]
final_limited_results.loc[
    mups2a_limited_results["pitch2"], "PM2THV1T"
] = mups2a_limited_results["t_dwell2"]

final_offset_results = pd.DataFrame(
    [], index=pitch_range, columns=("AACCCDPT", "FPTEMP_11", "PM2THV1T")
)
final_offset_results.loc[aca_offset_results["pitch2"], "AACCCDPT"] = \
    aca_offset_results["t_dwell2"]
final_offset_results.loc[fp_offset_results["pitch2"], "FPTEMP_11"] = \
    fp_offset_results[ "t_dwell2"]
final_offset_results.loc[
    mups2a_offset_results["pitch2"], "PM2THV1T"
] = mups2a_offset_results["t_dwell2"]

We’ll want to revisit the anchor values soon as well.

[60]:
anchor_dwell_data = (
    (
        aca_anchor_limited_pitch,
        aca.anchor_limited_time,
        aca_anchor_offset_pitch,
        aca.anchor_offset_time,
        dwell_limits.loc[aca_anchor_offset_pitch],
    ),
    (
        fp_anchor_limited_pitch,
        fp.anchor_limited_time,
        fp_anchor_offset_pitch,
        fp.anchor_offset_time,
        dwell_limits.loc[fp_anchor_offset_pitch],
    ),
    (
        mups2a_anchor_limited_pitch,
        mups2a.anchor_limited_time,
        mups2a_anchor_offset_pitch,
        mups2a.anchor_offset_time,
        dwell_limits.loc[mups2a_anchor_offset_pitch],
    ),
)
anchor_dwell_data = pd.DataFrame(
    anchor_dwell_data,
    index=("ACA", "ACIS FP", "MUPS2A"),
    columns=(
        "Anchor Limited Pitch",
        "Calculated Anchor Limited Time",
        "Anchor Offset Pitch",
        "Originally Assumed Available Offset Time",
        "Final Available Dwell Time",
    ),
)

Visualize the final maximum dwell data, and show which models are the lmiiting factors.

[61]:
def color_nan_gray(val):
    color = "rgba(175, 175, 175)" if np.isnan(val) else ""
    return "color: %s" % color


def highlight_cells(x):
    df = pd.DataFrame("", index=x.index, columns=x.columns)
    cell_style = "color: black; font-weight: bold; border-color: black; border-style: solid; border-width: 1px; border-collapse:collapse"
    df.loc[aca_anchor_limited_pitch, "ACA"] = cell_style
    df.loc[fp_anchor_limited_pitch, "ACIS FP"] = cell_style
    df.loc[mups2a_anchor_limited_pitch, "MUPS2A"] = cell_style
    return df


final_formatted_limited_results = pd.concat(
    (
        final_limited_results,
        final_limited_results.min(axis=1),
        final_limited_results.astype(np.float).idxmin(axis=1),
    ),
    axis=1,
)
final_formatted_limited_results.columns = (
    "ACA",
    "ACIS FP",
    "MUPS2A",
    "Max Dwell",
    "Limiting Model",
)

final_formatted_limited_results = (
    final_formatted_limited_results.style.applymap(
        color_nan_gray, subset=["ACA", "ACIS FP", "MUPS2A", "Max Dwell"]
    )
    .apply(highlight_cells, axis=None)
    .set_precision(0)
    .bar(
        subset=["ACA", "ACIS FP", "MUPS2A", "Max Dwell"],
        color="rgba(256, 0, 0, 0.25)",
        vmin=0,
        vmax=100000,
    )
)
display(
    HTML(
        '<p style="color: black; font-size: 30px; font-family: Arial, Helvetica, sans-serif;">Final Maximum Dwell Results</p>'
    )
)
display(final_formatted_limited_results)

Final Maximum Dwell Results

ACA ACIS FP MUPS2A Max Dwell Limiting Model
45 nan nan 18817 18817 PM2THV1T
50 nan nan 18003 18003 PM2THV1T
55 nan nan 17296 17296 PM2THV1T
60 162005 nan 16576 16576 PM2THV1T
65 72664 nan 17898 17898 PM2THV1T
70 46872 nan 19467 19467 PM2THV1T
75 34287 nan 21583 21583 PM2THV1T
80 28469 nan 25368 25368 PM2THV1T
85 24286 nan 31536 24286 AACCCDPT
90 21187 nan 42814 21187 AACCCDPT
95 21938 nan 46963 21938 AACCCDPT
100 22726 nan 51775 22726 AACCCDPT
105 23546 nan nan 23546 AACCCDPT
110 24472 nan nan 24472 AACCCDPT
115 26473 nan nan 26473 AACCCDPT
120 28797 nan nan 28797 AACCCDPT
125 33381 45088 nan 33381 AACCCDPT
130 39748 31943 nan 31943 FPTEMP_11
135 73617 27741 nan 27741 FPTEMP_11
140 nan 24525 nan 24525 FPTEMP_11
145 nan 22974 nan 22974 FPTEMP_11
150 nan 21737 nan 21737 FPTEMP_11
155 nan 19334 nan 19334 FPTEMP_11
160 nan 17458 nan 17458 FPTEMP_11
165 nan 17562 nan 17562 FPTEMP_11
170 nan 17675 nan 17675 FPTEMP_11
175 nan 15439 nan 15439 FPTEMP_11
180 nan 13688 nan 13688 FPTEMP_11

The plot above shows the following information - Maximum dwell times are shown in seconds - Pink bars show available maximum dwell time for each individual model as well as the overall maximum dwell time, for each limited pitch - The limiting model is shown in the last column - The maximum dwell time at the anchor limited pitch for each model is highlighted in bold with black borders - Durations for pitch values which do not heat a model are represented by NaN’s

You’ll notice that the max dwell times for each model, at their anchor limited pitch values, are slighty different compared to the original “anchor” values used to generate these results (repeated in the next table). This is expected and commonly observed. The anchor values shown below serve as the seed to produce the results shown above. Remember the dwell balancing algorithm uses the following steps: 1. Anchor offset time + pitch is used to find the anchor limited time at the anchor limited pitch in find_anchor_condition (first iteration uses a guess). 2. The anchor limited time + pitch is used to generate the general offset dwell curve. 3. The newly calculated general offset dwell curve is used to supply the offset dwell time at the anchor offset pitch to generate the general max dwell limit curve.

The general dwell curves are both calculated after the anchor values are known, and although the general limited dwell curve should ideally contain the exact anchor limited dwell value, due to how the dwell states are ordered they can shift slightly.

The current algorithm conveniently separates the calculation of both anchor values from the calculation of the final dwell curves, which matches the basic dwell balancing algorithm as it was originally presented, and helps separate the concepts, however we only need to start with an initial offset dwell guess and then directly calculate the general dwell curves. This will be considered as an enhancement in the future, however this step takes very little time and does not negatively impact the results so it will stay for now.

[62]:
anchor_dwell_data
[62]:
Anchor Limited Pitch Calculated Anchor Limited Time Anchor Offset Pitch Originally Assumed Available Offset Time Final Available Dwell Time
ACA 90 21209.654645 160 17371.675441 17371.675441
ACIS FP 170 17954.144825 60 16576.192900 16576.192900
MUPS2A 60 16572.032038 160 17371.675441 17371.675441

The plot below represents the dwell limits and associated cooling times for the following conditions:

  • Date: 2022:001:00:00:00

  • Roll: 0.0 Degrees

  • ACIS Chips: 4

  • ACA Limit: -6.5 C

  • ACIS FP Limit: -112.0 C

  • MUPS 2A Limit: 210.0F

Please note the following features:

  • The limited data are shown using solid lines and the associated offset data are shown using dashed lines.

  • The composite maximum dwell times can be determined by finding the minimum dwell limit value for each pitch.

  • Each model has offset time (dashed line) within/underneath the available limited time (solid line) for at least one pitch value.

[63]:
plot_data = generate_limited_offset_results_plot_data(
    final_limited_results, final_offset_results
)
plot_object = generate_limited_offset_results_timbre_plot(
    plot_data, "Final Timbre Dwell Durations", y_max=200000
)
pio.show(plot_object)
50100150050k100k150k200k
Composite Dwell LimitAACCCDPT: Dwell LimitAACCCDPT: Offset DwellFPTEMP_11: Dwell LimitFPTEMP_11: Offset DwellPM2THV1T: Dwell LimitPM2THV1T: Offset DwellFinal Timbre Dwell DurationsPitchDwell Duration

Automated calculation using built-in method

The data required to produce a composite maximum dwell limit with associated offset times can be produced using the built-in method rather than stepwise as above. Simply defining a timbre_object as an instance of the Composite class with the required input information will yield this information.

  • The max_dwell is the initial guess for the available anchor offset time, and does not cap the final required offset time curve.

  • The pitch step defines the resolution of the final dwell curves. This pitch step must result in a set of pitch values that includes the anchor points (currently 45, 60, 90, 160, 170, 175) so either 1 or 5 degrees is usually chosen.

  • As indicated before, the results cacluated are only valid for the input conditions supplied. If one wants to estimate the range of available dwell times for different conditions, each condition would need to be run separately.

[64]:
limits = {
    "fptemp_11": -112.0,
    "1dpamzt": 37.5,
    "1deamzt": 37.5,
    "1pdeaat": 52.5,
    "aacccdpt": -6.5,
    "4rt700t": f_to_c(100.0),
    "pftank2t": f_to_c(110.0),
    "pm1thv2t": f_to_c(210.0),
    "pm2thv1t": f_to_c(210.0),
    "pline03t": f_to_c(50.0),
    "pline04t": f_to_c(50.0),
}

date = "2022:001:00:00:00"
chips = 4
roll = 0

timbre_object = Composite(date, chips, roll, limits, max_dwell=200000, pitch_step=5)
------------------------------------------------------------------------------------------------------------------------
Map Dwell Capability
------------------------------------------------------------------------------------------------------------------------

----------------------------------------
Start of Iteration:

1pdeaat is not limited at a pitch of 45 degrees near 2022:001:00:00:00.000, with the following constant conditions:
{'roll': 0, 'fep_count': 4, 'ccd_count': 4, 'clocking': True, 'vid_board': True, 'sim_z': 100000, 'dh_heater': False}.

Approximate dwell limits calculated by this iteration:
  Pitch    Duration
     45    21311.19
     60    18495.64
     90    28856.45
    160    24596.29
    170    21221.50
    175    18785.26

Tail sun time available for cooling is less than originally assumed at the start of this iteration, start new iteration.
Forward sun time available for cooling is less than originally assumed at the start of this iteration, start new iteration.

----------------------------------------
Start of Iteration:

1pdeaat is not limited at a pitch of 45 degrees near 2022:001:00:00:00.000, with the following constant conditions:
{'roll': 0, 'fep_count': 4, 'ccd_count': 4, 'clocking': True, 'vid_board': True, 'sim_z': 100000, 'dh_heater': False}.

Approximate dwell limits calculated by this iteration:
  Pitch    Duration
     45    19357.85
     60    17022.98
     90    22889.28
    160    18922.21
    170    19375.23
    175    17048.81

Forward sun time available for cooling is less than originally assumed at the start of this iteration, start new iteration.

----------------------------------------
Start of Iteration:

1pdeaat is not limited at a pitch of 45 degrees near 2022:001:00:00:00.000, with the following constant conditions:
{'roll': 0, 'fep_count': 4, 'ccd_count': 4, 'clocking': True, 'vid_board': True, 'sim_z': 100000, 'dh_heater': False}.

Approximate dwell limits calculated by this iteration:
  Pitch    Duration
     45    18815.60
     60    16574.56
     90    21569.45
    160    17752.84
    170    18101.96
    175    15819.62

Forward sun time available for cooling is less than originally assumed at the start of this iteration, start new iteration.

----------------------------------------
Start of Iteration:

1pdeaat is not limited at a pitch of 45 degrees near 2022:001:00:00:00.000, with the following constant conditions:
{'roll': 0, 'fep_count': 4, 'ccd_count': 4, 'clocking': True, 'vid_board': True, 'sim_z': 100000, 'dh_heater': False}.

Approximate dwell limits calculated by this iteration:
  Pitch    Duration
     45    18815.60
     60    16574.56
     90    21261.60
    160    17358.57
    170    17691.17
    175    15420.21

Forward sun time available for cooling is less than originally assumed at the start of this iteration, start new iteration.

----------------------------------------
Start of Iteration:

1pdeaat is not limited at a pitch of 45 degrees near 2022:001:00:00:00.000, with the following constant conditions:
{'roll': 0, 'fep_count': 4, 'ccd_count': 4, 'clocking': True, 'vid_board': True, 'sim_z': 100000, 'dh_heater': False}.

Approximate dwell limits calculated by this iteration:
  Pitch    Duration
     45    18815.60
     60    16574.56
     90    21261.60
    160    17358.57
    170    17691.17
    175    15420.21

------------------------------------------------------------------------------------------------------------------------
Fill In Dwell Capability
------------------------------------------------------------------------------------------------------------------------

1pdeaat is not limited at a pitch of 45 degrees near 2022:001:00:00:00.000, with the following constant conditions:
{'roll': 0, 'fep_count': 4, 'ccd_count': 4, 'clocking': True, 'vid_board': True, 'sim_z': 100000, 'dh_heater': False}.

------------------------------------------------------------------------------------------------------------------------
Final Dwell Limits:
  Pitch    Duration
     45    18815.60
     50    18008.73
     55    17254.66
     60    16574.56
     65    17901.64
     70    19494.26
     75    21566.47
     80    23448.97
     85    24349.92
     90    21261.60
     95    21967.88
    100    22777.56
    105    23547.11
    110    24499.91
    115    26535.18
    120    28879.91
    125    32368.38
    130    24381.99
    135    27630.23
    140    24655.43
    145    22971.22
    150    21764.88
    155    19116.02
    160    17358.57
    165    17540.71
    170    17691.17
    175    15420.21
    180    13767.29


The plot below shows the maximum dwell data calculated for each model as well as the composite limitations: - Maximum dwells are shown in black or red. - Cases which serve as limiting factors are shown in red. - The model that serves as the limiting factor for each pitch is shown in the last column. - Anchor values are shown in bold with black borders. - Cases where a limit is not achieved is shown with a gray “nan”. - Red bars provide visual cues on how long each case can dwell at each pitch.

NOTE: Anchor limited values shown below do not need to be a limiting factor, what is important is that sufficient offset time exists to support these dwell times. These data are shown in the next plot.

[65]:
final_formatted_limited_results = visualize_final_dwell_limits(timbre_object)
display(
    HTML(
        '<p style="color: black; font-size: 30px; font-family: Arial, Helvetica, sans-serif;">Final Maximum Dwell Times</p>'
    )
)
display(final_formatted_limited_results)

Final Maximum Dwell Times

1dpamzt 1deamzt fptemp_11 1pdeaat aacccdpt pm1thv2t pm2thv1t 4rt700t pftank2t pline03t pline04t Max Dwell Limiting Model
45 nan nan nan nan nan 29616 18840 nan 28014 nan nan 18840 pm2thv1t
50 nan nan nan nan nan 28130 18009 nan 26594 nan nan 18009 pm2thv1t
55 nan nan nan nan nan 26364 17255 nan 25238 nan nan 17255 pm2thv1t
60 nan nan nan nan 162005 25165 16583 106762 24183 nan nan 16583 pm2thv1t
65 nan nan nan nan 72813 25040 17902 42787 23986 nan nan 17902 pm2thv1t
70 nan nan nan nan 46865 24483 19494 27069 23785 nan nan 19494 pm2thv1t
75 nan nan nan nan 34394 23816 21566 32646 23600 nan nan 21566 pm2thv1t
80 nan nan nan nan 28511 nan 25371 41241 23449 nan nan 23449 pftank2t
85 nan nan nan nan 24350 nan 31563 28243 26685 nan nan 24350 aacccdpt
90 nan nan nan nan 21262 nan 42576 21526 30874 nan nan 21262 aacccdpt
95 nan nan nan nan 21968 nan 47001 24727 36738 nan nan 21968 aacccdpt
100 nan nan nan nan 22778 nan 51759 28957 45113 nan nan 22778 aacccdpt
105 nan nan nan nan 23547 nan nan 27339 43463 nan nan 23547 aacccdpt
110 nan nan nan nan 24500 nan nan 26163 41796 nan nan 24500 aacccdpt
115 nan nan nan nan 26535 nan nan 33832 39989 nan nan 26535 aacccdpt
120 nan nan nan nan 28880 nan nan 47633 38791 nan nan 28880 aacccdpt
125 nan nan 45037 nan 33413 nan nan 32368 43565 nan nan 32368 4rt700t
130 nan nan 32002 nan 39664 nan nan 24382 48770 nan nan 24382 4rt700t
135 65848 nan 27630 nan 73727 nan nan 42295 297197 nan nan 27630 fptemp_11
140 35757 62866 24655 nan nan nan nan 190356 nan nan nan 24655 fptemp_11
145 32160 38712 22971 nan nan nan nan nan nan nan nan 22971 fptemp_11
150 28913 29998 21765 nan nan nan nan nan nan nan nan 21765 fptemp_11
155 28525 31553 19116 nan nan nan nan nan nan nan nan 19116 fptemp_11
160 28026 32813 17359 nan nan nan nan nan nan nan 65270 17359 fptemp_11
165 27359 37198 17541 nan nan nan nan nan nan nan 34045 17541 fptemp_11
170 26911 43646 17691 nan nan nan nan nan nan nan 20389 17691 fptemp_11
175 26911 43646 15420 nan nan nan nan nan nan 18166 18918 15420 fptemp_11
180 26911 43646 13767 nan nan nan nan nan nan 19588 18046 13767 fptemp_11

The plot below shos the offset dwell data associated with the limited data shown above: - Minimum dwells are shown in black or blue. - Cases where the offset time exists within all limited dwell times are shown in blue. - Anchor values are shown in bold blue with black borders. - Cases where a limit is not achieved is shown with a gray “nan”. - Blue bars provide visual cues on how long each case needs to dwell to offset the limited dwells shown above.

[66]:
final_formatted_limited_results = visualize_final_dwell_limits(timbre_object)
display(
    HTML(
        '<p style="color: black; font-size: 30px; font-family: Arial, Helvetica, sans-serif;">Final Offset Dwell Times</p>'
    )
)
visualize_final_dwell_offsets(timbre_object, max_plot_limit=100000)

Final Offset Dwell Times

[66]:
1dpamzt 1deamzt fptemp_11 1pdeaat aacccdpt pm1thv2t pm2thv1t 4rt700t pftank2t pline03t pline04t
45 113317 10186 14468 nan 24552 nan nan 23927 nan 16083 19633
50 156332 10314 14867 nan 38547 nan nan 46578 nan 16378 18245
55 13131 10553 14959 nan 106721 nan nan nan nan 16473 17215
60 14085 10769 15027 nan nan nan nan nan nan 16568 16512
65 14383 10944 15095 nan nan nan nan nan nan 17033 15672
70 15090 11295 15322 nan nan nan nan nan nan 17365 14936
75 15190 11507 16464 nan nan nan nan nan nan 17912 14297
80 16279 11905 17454 nan nan nan nan nan nan 19485 14431
85 16358 12117 18403 nan nan nan nan nan nan 21332 14732
90 16880 12537 19958 nan nan nan nan nan nan 24035 14954
95 21908 15712 25949 nan nan nan nan nan nan 25420 15795
100 33642 20859 39472 nan nan nan nan nan nan 27205 16664
105 nan 34188 nan nan nan nan nan nan nan 29280 17715
110 nan nan nan nan nan nan nan nan nan 31862 18831
115 nan nan nan nan nan nan nan nan nan 35455 20182
120 nan nan nan nan nan nan nan nan nan 40607 21661
125 nan nan nan nan nan nan nan nan nan nan 23636
130 nan nan nan nan nan nan nan nan nan nan 26377
135 nan nan nan nan nan nan nan nan nan nan 34389
140 nan nan nan nan nan nan nan nan 69682 nan 50327
145 nan nan nan nan 87173 nan 89507 nan 47267 nan 101728
150 nan nan nan nan 40578 nan 40017 63605 35729 nan nan
155 nan nan nan nan 24345 30372 24452 27100 23378 nan nan
160 nan nan nan nan 17412 16667 17406 17291 17343 nan nan
165 nan nan nan nan 12855 9332 10589 12764 14886 nan nan
170 nan nan nan nan 10076 4469 8541 10087 13035 nan nan
175 nan nan nan nan 8527 2569 7603 8317 13330 nan nan
180 nan nan nan nan 7459 2569 6985 7100 13584 nan nan

The plot below shows both the limited and the associated offset dwell data for each model. Limited data are shown in solid lines, offset data are shown using dashed lines.

[67]:
plot_data = generate_limited_offset_results_plot_data(
    timbre_object.limited_results, timbre_object.offset_results
)
plot_object = generate_limited_offset_results_timbre_plot(
    plot_data, "Final Timbre Dwell Durations", y_max=200000
)
pio.show(plot_object)
50100150050k100k150k200k
Composite Dwell Limit1DPAMZT: Dwell Limit1DPAMZT: Offset Dwell1DEAMZT: Dwell Limit1DEAMZT: Offset DwellFPTEMP_11: Dwell LimitFPTEMP_11: Offset Dwell1PDEAAT: Dwell Limit1PDEAAT: Offset DwellAACCCDPT: Dwell LimitAACCCDPT: Offset DwellPM1THV2T: Dwell LimitPM1THV2T: Offset DwellPM2THV1T: Dwell LimitPM2THV1T: Offset Dwell4RT700T: Dwell Limit4RT700T: Offset DwellPFTANK2T: Dwell LimitPFTANK2T: Offset DwellPLINE03T: Dwell LimitPLINE03T: Offset DwellPLINE04T: Dwell LimitPLINE04T: Offset DwellFinal Timbre Dwell DurationsPitchDwell Duration

The plot above presents an interesting case worth exploring. The offset time for the DPA seems to increase to a very high value at approximately 55 degrees, where one would expect it to remain very low, as 55 degrees is a known cooling region for the DPA. We can recreate this case and use one of the plots used earlier to see what the temperature profile looks like. Please note that we are using n_dwells=30 as this is what is currently used by default (n_dwells=10 was used earlier to make visualizing the process easier).

As can be seen in the plot below, the 50 degree dwell easily reaches a steady state cold value and remains there longer than it needs to in order to offset the limited dwell at 170 degrees pitch. Additionally the model heats relatively fast at a dwell of 170 degrees pitch (approximately 27 Ks). It seems as if this case reveals a weakness in the dwell estimation algorithm. When the available heating dwell time becomes decoupled from the required offset time, due to the offset dwell reaching a steady state cooling temperature, the required offset time to balance a given limited dwell is not necessarily accurate. These cases are relatively rare, are only likely to be observed using the ACIS models, and can be identified by observing such deviations from an existing trend. These existing trends will be more accurate in cases where an offset steady state value is not reached, and can serve to establish a baseline from which one can observe such deviations. This can be tested by changing the offset pitch values to other pitch values, such as 80 degrees, in the code below and observing the output.

[68]:
date = "2022:001:00:00:00"
limited_pitch = 170
offset_pitch = 50

dwell1_state = {"pitch": offset_pitch}
dwell2_state = {"pitch": limited_pitch}
t_dwell1 = timbre_object.offset_results.loc[offset_pitch, "1dpamzt"]
t_dwell2 = timbre_object.limited_results.loc[limited_pitch, "1dpamzt"]

msid = "1dpamzt"
limit = 37.5
chips = 4

model_spec, dpa_md5 = get_local_model("../timbre/tests/data/dpa_spec.json")
init = {
    "1dpamzt": limit,
    "dpa0": limit,
    "eclipse": False,
    "roll": 0.0,
    "fep_count": chips,
    "ccd_count": chips,
    "clocking": True,
    "vid_board": True,
    "sim_z": 100000,
    "dpa_power": 0.0,
}
limit_type = "max"
duration = 2592000
t_backoff = 1725000
n_dwells = 30

datesecs = CxoTime(date).secs

# This ensures three "cycles" of the two dwell states, within the portion of
# the schedule used for evaluation
# Subtract 1000 sec for extra padding.
max_dwell = (t_backoff - t_dwell1) / 3 - 1000

model_results, state_times, state_keys = calc_binary_schedule(
    datesecs,
    dwell1_state,
    dwell2_state,
    t_dwell1,
    t_dwell2,
    msid,
    model_spec,
    init,
    duration=duration,
    t_backoff=t_backoff,
)

tstart = model_results[msid].times[0]
tstop = model_results[msid].times[-1]
state_data = {"state_times": state_times, "state_keys": state_keys}
plot_data = format_plot_data(
    model_results[msid], limit, state_data, dwell1_state, dwell2_state
)
shapes = format_shapes(state_data)
shapes.extend(gen_unused_range(tstart, tstop))
annotations = gen_range_annotations(tstart, tstop, limit + 1, limit + 1 + 0.01)
annotations.extend(
    gen_shading_annotation(
        "2022:009:12:00:00",
        30,
        f"Pitch: {dwell1_state['pitch']}",
        f"Pitch: {dwell2_state['pitch']}",
    )
)
plot_dict = generate_converged_solution_plot_dict(
    plot_data, shapes, annotations, tstart, tstop
)
pio.show(plot_dict)
19 Dec2021:35326 Dec2021:360 2 Jan2022:002 9 Jan2022:009State 1State 210152025303540
Converged Timbre Dwell SimulationSimulation Temperature(Celsius)Data Range used for evaluationLightly Shaded Vertical Bands = Dwell State #1 (Pitch: 50)Unshaded Vertical Bands = Dwell State #2 (Pitch: 170)