Py.Cafe

ianmarr/

streamlit_app

Streamlit on Py.cafe: Interactive Component Examples

DocsPricing
  • FuelStudyData/
  • TOFLStudyData/
  • app.py
  • requirements.txt
  • streamlit_app.py
streamlit_app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# imports
import os
import streamlit as st
import plotly.express as px
import pandas as pd

import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

def calc_metrics(df: pd.DataFrame, runway: pd.DataFrame):
    """Calculates metrics to be used for plots"""
    tech_aircraft = df['tech_aircraft'].unique().tolist() # Populate all combinations
    # Calculate metrics for plots
    # Determine mission feasibility
    df.loc[df['departure_airport_code'].isin(runway['AIRPORT_IATA']), 'Dep RWY Length [m]']= runway.set_index('AIRPORT_IATA').loc[df['departure_airport_code'],'RUNWAY_TAKE_OFF_LENGTH_m'].values
    df.loc[df['arrival_airport_code'].isin(runway['AIRPORT_IATA']), 'Arr RWY Length [m]']= runway.set_index('AIRPORT_IATA').loc[df['arrival_airport_code'],'RUNWAY_TAKE_OFF_LENGTH_m'].values
    df['TOFL feasible for mission']= (df['Dep RWY Length [m]'] > df['TechAircraft.TOFL.TOFL'])&(df['Arr RWY Length [m]'] > df['TechAircraft.TOFL.TOFL'])

    # Column headers

    headers = ['Max. MTOW [kg]', 'Max. MLW [kg]', 'Avg. nPAX', 'Sum. of COC RUNWAY', 'Avg. of TOFL [m]', 'Avg. of Vapp [kts]',
               'Sum. of DOC_BlockFuel', 'Avg. of COC', 'Sum. of COC Fuel', 'Sum. of COC MAINTENANCE'
               , 'Sum. of COC NAVIGATION', 'Sum. of COC RAMP', 'Avg. of Equivalent_Thrust [lbf]', 'TechAircraft.Results.Missions.DOC.DOC_Range',
                'TechAircraft.TOFL.TOFL', 'TechAircraft.Results.Missions.DOC.DOC_BlockFuel', 'TOFL feasible for mission']
    for spec in tech_aircraft:
        df.loc[df['tech_aircraft']== spec, 'Max. MTOW [kg]'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.Mission Data.MTOW'].max()
        df.loc[df['tech_aircraft']== spec, 'Max. MLW [kg]'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.Mission Data.MLW'].max()
        df.loc[df['tech_aircraft']== spec, 'Avg. nPAX'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.Mass Derivation inputs.nPAX'].mean()
        df.loc[df['tech_aircraft']== spec, 'Sum. of COC RUNWAY'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.RUNWAY'].sum()
        # df.loc[df['tech_aircraft']== spec, 'Sum. of TOFL infeasible for mission'] = df['TechAircraft.TOFL.TOFL infeasible for this mission'].sum()["INFEASIBLE"] 
        df.loc[df['tech_aircraft']== spec, 'Avg. of TOFL [m]']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.TOFL.TOFL'].mean()
        df.loc[df['tech_aircraft']== spec, 'Avg. of Vapp [kts]']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.TOFL.Vapp'].mean()
        df.loc[df['tech_aircraft']== spec, 'Sum. of DOC_BlockFuel']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.Results.Missions.DOC.DOC_BlockFuel'].sum()
        df.loc[df['tech_aircraft']== spec, 'Avg. of COC']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.COMPLETE_COC'].mean()
        df.loc[df['tech_aircraft']== spec, 'Sum. of COC Fuel']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.FUEL'].sum()
        df.loc[df['tech_aircraft']== spec, 'Sum. of COC MAINTENANCE']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.MAINTENANCE'].sum()
        df.loc[df['tech_aircraft']== spec, 'Sum. of COC NAVIGATION']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.NAVIGATION'].sum()
        df.loc[df['tech_aircraft']== spec, 'Sum. of COC RAMP']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.RAMP_HANDLING'].sum()
        df.loc[df['tech_aircraft']== spec, 'Avg. of Equivalent_Thrust [lbf]']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.TOFL.Airbus_equivalent_thrust'].mean()

    final_columns = ['tech_aircraft'] + ['Fuel', 'TOFL', 'Engine', 'Range', 'Year'] + headers
    df = df[final_columns].drop_duplicates()
    df['Range'] = df['Range'].str.extract('(\d+)')
    df = df.sort_values(["Year"], ascending=[True])
    # df = df.sort_values(["Year", "Range"], ascending=[True, True])
    return df
    # Remove unnecessary columns

def plot_SliderScatter(df: pd.DataFrame, xlabel: str, ylabel: str):
    fig = px.scatter(df, x=xlabel, y=ylabel, 
                     animation_frame="Year", 
                     animation_group="tech_aircraft",
                     color="TOFL",
                     symbol = "Fuel",
                     hover_name="tech_aircraft",
                    #  text = "Engine",
                     title=(xlabel + " vs " + ylabel + " overtime") 
                     )
    fig.update_traces(textposition='top center')
    fig["layout"].pop("updatemenus") # optional, drop animation buttons
    return fig

def plot_3D(df: pd.DataFrame, xlabel: str, ylabel: str, zlabel: str):
    fig = go.Figure()

    colors = px.colors.qualitative.Plotly  # Default qualitative color scheme
    symbols = ['circle', 'square', 'diamond', 'cross', 'x']  # Default symbols    
    fig = px.scatter_3d(df, x=xlabel, y=ylabel, z=zlabel,
                     animation_frame="Year",
                     animation_group="tech_aircraft",
                     symbol = "Fuel",
                     color="TOFL",
                     hover_name="tech_aircraft",
                    #  text = "Engine",
                     title=(xlabel + " vs " + ylabel + " overtime") 
                     )
    fig.update_traces(textposition='top center')
    fig.update_layout(scene_camera=dict(eye=dict(x=2, y=2, z=0.5)))

    # fig.update_layout(scene=dict(
    #     yaxis=dict(range=[df[ylabel].min(), df[ylabel].max()]), 
    #     zaxis=dict(range=[df[zlabel].min(), df[zlabel].max()]) 
    # ))
    # fig["layout"].pop("updatemenus") # optional, drop animation buttons
    return fig

# File reading

# Get script directory
# script_dir = os.path.dirname(os.path.abspath(__file__))
# os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz-12.1.0-win64/bin/' # Solve graphviz path problem

st.set_page_config(page_title="Hackathon Results Visualisation", layout="centered")
def parameters_study_page():
    upload_file = st.file_uploader(label="Design point output files in .csv format.", type="csv", accept_multiple_files=True)
    file_flag = [0,0]
    if len(upload_file) != 0:
        dfs =[pd.read_csv(f) for f in upload_file]
        df = pd.concat(dfs, ignore_index=True)
        df[['Fuel', 'TOFL', 'Engine', 'Range', 'Year']] = df['tech_aircraft'].str.extract(r'([^_]+)_([^_]+)_([^_]+)_([^_]+)_([^_]+)')
        file_flag[0] = 1
    runway_file = st.file_uploader(label="Runway data csv", type="csv")
    if runway_file is not None:
        df_runway = pd.read_csv(runway_file)
        file_flag[1] = 1
    if file_flag[0]==1 & file_flag[1]==1:
        st.write("Files uploaded.")
        df = calc_metrics(df, df_runway)
        with st.form(key="Select parameters"):
                heads = list(df.columns.values)
                params = st.multiselect("Select parameters to compare against: (Default: 'Range', 'TechAircraft.Results.Missions.DOC.DOC_Range', 'TechAircraft.TOFL.TOFL')", heads, ['Range', 'TechAircraft.Results.Missions.DOC.DOC_Range', 'TechAircraft.TOFL.TOFL'], max_selections=3)
                plot_button = st.form_submit_button("Generate 3D plot")
        if st.button("Plot"):
            st.plotly_chart(plot_SliderScatter(df, 'Avg. of TOFL [m]', 'Avg. of Equivalent_Thrust [lbf]'), use_container_width=True)
            st.plotly_chart(plot_SliderScatter(df, 'Avg. of TOFL [m]', 'Max. MTOW [kg]'), use_container_width=True)
            st.plotly_chart(plot_3D(df, params[0], params[1], params[2]), use_container_width=True)
            st.toast("Plots completed!")
    else:
        st.write("Input files incomplete.")

def Fuel_Fleet_study_page():
    colour_group = px.colors.qualitative.Plotly # Change this if we have more fuel/TOFL/engine options
    def update (change):
        # Example of dynamically downselection using sliders.
        if change == 'Kero':
            st.session_state.LH2 = st.session_state.Kero / 2
            st.session_state.SAF = st.session_state.Kero / 2
        else:
            st.session_state.Kero = st.session_state.LH2 + st.session_state.SAF
        
    def calcStep(df, Col:str) -> int:
        df_ans = (df[Col].max()-df[Col].min())/(df[Col].unique().size-1)
        return df_ans.astype('int64')

    upload_file = st.file_uploader(label="CCC Fuel price files in .csv format.", type="csv", accept_multiple_files=True)

    if len(upload_file) != 0:
        dfs =[pd.read_csv(f) for f in upload_file]
        for df in dfs:
            df['Year'] = df.columns.values[0]
            df.drop(df.columns[0], axis=1, inplace=True)
        df = pd.concat(dfs, ignore_index=True)

        with st.form(key="Fleet mixing"):
            KeroInput = st.slider('Price of Kerosene [$/USG]', df['Kerosene price [$/USG]'].min(), df['Kerosene price [$/USG]'].max(), value=2, step=calcStep(df, 'Kerosene price [$/USG]'), key='Kero')
            LH2Input = st.slider('Price of LH2 [$/USG]', df['LH2 price [$/USG]'].min(), df['LH2 price [$/USG]'].max(), value=2, step=calcStep(df, 'LH2 price [$/USG]'), key='LH2')
            SAFInput = st.slider('Price of SAF [$/USG]', df['SAF price [$/USG]'].min(), df['SAF price [$/USG]'].max(), value=2, step=calcStep(df, 'SAF price [$/USG]'), key='SAF')
            NH3Input = st.slider('Price of NH3 [$/USG]', df['NH3 price [$/USG]'].min(), df['NH3 price [$/USG]'].max(), value=2, step=calcStep(df, 'NH3 price [$/USG]'), key='NH3')
            MethanolInput = st.slider('Price of Methanol [$/USG]', df['CH3OH price [$/USG]'].min(), df['CH3OH price [$/USG]'].max(), value=2, step=calcStep(df, 'CH3OH price [$/USG]'), key='CH3OH')

            df_ans = df[(df['Kerosene price [$/USG]'] == KeroInput) & (df['LH2 price [$/USG]']==LH2Input) & (df['SAF price [$/USG]'] == SAFInput) & (df['NH3 price [$/USG]'] == NH3Input) & (df['CH3OH price [$/USG]'] == MethanolInput)]
            df_ans.reset_index(drop=True, inplace=True)
            st.form_submit_button("Find current scenario")
            if df_ans.empty:
                st.warning("Current scenario does not exist, please try again 🙁")
            else:
                st.success("Congrats for finding a valid scenario, current chance: " + str(round(len(df[df['Year'] == df['Year'].unique()[0]])/(df['Kerosene price [$/USG]'].unique().size**5), 2)), icon="🍀")
        
        if not df_ans.empty:
            st.write(df_ans)
            fig = make_subplots(
                rows=2, cols=3,
                column_widths=[0.6, 0.2, 0.2],
                row_heights=[1, 1],
                specs=[[{"type": "bar"}, {"type": "pie"}, {"type": "pie"}],
                    [{"type": "bar"}, {"type": "pie"}, {"type": "pie"}]],
                    subplot_titles=("Scheduled no. of Flights", "Fleet % by Fuel", "", "Fuel Consumption [kg]", "Fleet % by TOFL", "Fleet % by Engine"))

            columns_BarPlot = ["Scheduled number of Kerosene flights", "Scheduled number of CH3OH flights", "Scheduled number of LH2 flights","Scheduled number of NH3 flights","Scheduled number of SAF flights"]
            columns_2nd_BarPlot = ["Kerosene fuel consumption across all bases [kg]","CH3OH fuel consumption across all bases [kg]","LH2 fuel consumption across all bases [kg]","NH3 fuel consumption across all bases [kg]","SAF fuel consumption across all bases [kg]"]
            columns_fleet_PieChart = ["Fleet size (Kerosene) after scheduling","Fleet size (CH3OH) after scheduling","Fleet size (LH2) after scheduling","Fleet size (NH3) before after scheduling","Fleet size (SAF) after scheduling"]
            TOFL_PieChart = ["Fleet size (TOFL1) after scheduling","Fleet size (TOFL2) after scheduling","Fleet size (TOFL3) after scheduling"]
            Engine_PieChart = ["Fleet size (Engine1) after scheduling","Fleet size (Engine2) after scheduling"]
            fuelTypes = ["Kerosene", "CH3OH", "LH2", "NH3", "SAF"]
            years = df_ans["Year"].unique().tolist()
            
            j = 0
            for i, year in enumerate(years):
                for i in range(len(fuelTypes)):
                    fig.add_trace(
                        go.Bar(x = [fuelTypes[i]], y=[df_ans.loc[j, columns_BarPlot[i]]], name=fuelTypes[i] + " at " + year, marker=dict(color=colour_group[i]) ),
                        row=1, col=1,
                    )
                
                for i in range(len(fuelTypes)):
                    fig.add_trace(
                        go.Bar(x = [fuelTypes[i]], y=[df_ans.loc[j, columns_2nd_BarPlot[i]]], name=fuelTypes[i] + " at " + year, marker=dict(color=colour_group[i]), showlegend=False),
                        row=2, col=1,
                    )

                fig.add_trace(
                    go.Pie( labels=fuelTypes, values=df_ans.loc[j, columns_fleet_PieChart], marker=dict(colors=colour_group), showlegend=False),
                    row=1, col=2,
                )

                fig.add_trace(
                    go.Pie( labels=TOFL_PieChart, values=df_ans.loc[j, TOFL_PieChart], marker=dict(colors=colour_group[5:8])), # Change this if we have more fuel/TOFL/engine options
                    row=2, col=2,
                )

                fig.add_trace(
                    go.Pie( labels=Engine_PieChart, values=df_ans.loc[j, Engine_PieChart], marker=dict(colors=colour_group[8:])), # Change this if we have more fuel/TOFL/engine options
                    row=2, col=3,
                )
                j = j + 1
            
            steps = []
            for i in range(0,len(fig.data),len(fuelTypes)*2+3):
                step = dict(
                    method="update",
                    args=[{"visible": [False] * len(fig.data)},
                        {"title": "Current Year: " + years[int(i/(len(fuelTypes)*2+3))]},
                          {"label": years[int(i/(len(fuelTypes)*2+3))]}],  # layout attribute
                    label= years[int(i/(len(fuelTypes)*2+3))]
                )
                for k in range(i, i+len(fuelTypes)*2+3):
                    step["args"][0]["visible"][k] = True  # Toggle i to i+k'th traces to "visible"
                steps.append(step)

            sliders = [dict(
                active=0,
                currentvalue={"prefix": "Current Year: " },
                pad={"t": 50},
                steps=steps,
            )]

            fig.update_layout(
                sliders=sliders
            )

            st.plotly_chart(fig)

pages = {
    "Page 1": parameters_study_page,
    "Fleet Mixing study": Fuel_Fleet_study_page,
}

selection = st.sidebar.radio("GoTo", list(pages.keys()))

# Page rendering
page = pages[selection]
page()