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]:
msid | date | datesecs | limit | t_dwell1 | t_dwell2 | min_temp | mean_temp | max_temp | min_pseudo | mean_pseudo | max_pseudo | converged | unconverged_hot | unconverged_cold | hotter_state | colder_state | pitch1 | eclipse1 | pitch2 | eclipse2 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
str20 | str8 | float64 | float64 | float64 | float64 | float64 | float64 | float64 | float64 | float64 | float64 | bool | bool | bool | int8 | int8 | int64 | bool | int64 | bool |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -12.701908209660902 | -12.662815058213544 | -12.638279647107051 | nan | nan | nan | False | False | True | 1 | 2 | 45 | False | 45 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -10.754262032742414 | -10.478496753866102 | -10.370522019334915 | nan | nan | nan | False | False | True | 2 | 1 | 45 | False | 50 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -8.798103778770468 | -8.285324033439833 | -8.099490257052867 | nan | nan | nan | False | False | True | 2 | 1 | 45 | False | 55 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 112262.86182175252 | -7.215625886759161 | -6.8844363747404635 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 45 | False | 60 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 53485.8401238784 | -7.017587314447216 | -6.7811388893354385 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 45 | False | 65 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 34897.288613469536 | -6.916623611692971 | -6.716555616870003 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 45 | False | 70 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 25688.36697490581 | -6.872076377956675 | -6.686879998434366 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 45 | False | 75 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 21920.29340453202 | -6.854834583007448 | -6.6800153492971655 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 45 | False | 80 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 19200.986701918977 | -6.830194229487504 | -6.661476900204302 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 45 | False | 85 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 16975.82687247957 | -6.829009371685877 | -6.661592013463294 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 45 | False | 90 | False |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | 135864.2576042953 | -8.811710086766464 | -7.7269277505322504 | -6.5 | nan | nan | nan | True | False | False | 2 | 1 | 180 | False | 135 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -9.520672123247374 | -7.4792843670516485 | -6.792596923480321 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 140 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -11.056245070136672 | -9.227175305920179 | -8.611526489053057 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 145 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -12.571162710864538 | -10.969741242820325 | -10.430377873464037 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 150 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -14.605129665817923 | -13.343158190521978 | -12.915498733847313 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 155 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -16.57557266738169 | -15.694229156483809 | -15.400266370757095 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 160 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -18.387547516394772 | -17.81370171909465 | -17.648499750591704 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 165 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -19.926845471632735 | -19.582578916282927 | -19.48042740560264 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 170 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -21.068476515559613 | -20.898499819555653 | -20.848218414810145 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 175 | False |
aacccdpt | 2021:001 | 725846469.184 | -6.5 | 20000.0 | nan | -22.21811822994504 | -22.21442072282838 | -22.207465924798406 | nan | nan | nan | False | False | True | 2 | 1 | 180 | False | 180 | False |
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)
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)
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.
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.
Determine the cooling time to support this duration at all attitudes (we will only plot converged results).
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.
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.
As a sanity check, back calculate the associated cooling times using each pitch + dwell 2 time output from step 4.
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)
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)
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)
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)
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)
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)
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)