39#include <acfutils/perf.h>
42#define SECS_PER_HR 3600
44#define ACFT_PERF_MIN_VERSION 1
45#define ACFT_PERF_MAX_VERSION 1
46#define MAX_LINE_COMPS 2
53#define SECS_PER_STEP 10
57#define SECS_PER_STEP_TAKEOFF 1
61#define SECS_PER_STEP_DECEL 1
65#define SECS_PER_STEP_CRZ 10
67#define KCAS_THRESH 0.1
68#define KCAS_TABLE_THRESH 5
70#define MAX_ITER_STEPS 100000
106static bool_t step_debug = B_FALSE;
108static bool_t perf_table_parse(FILE *fp, perf_table_set_t *set,
109 unsigned num_eng,
double ff_corr,
unsigned *line_num);
113lacf_set_perf_step_debug(bool_t flag)
119lacf_get_perf_step_debug(
void)
125perf_isas_compar(
const void *a,
const void *b)
129 if (ia->isa < ib->isa)
131 if (ia->isa > ib->isa)
138perf_tables_ias_compar(
const void *a,
const void *b)
144 if (ta->ias < tb->ias)
146 if (ta->ias > tb->ias)
153perf_tables_mach_compar(
const void *a,
const void *b)
159 if (ta->mach < tb->mach)
161 if (ta->mach > tb->mach)
167static perf_table_set_t *
168perf_table_set_alloc(
void)
170 perf_table_set_t *ts =
safe_calloc(1,
sizeof (*ts));
179perf_table_set_free(perf_table_set_t *ts)
197 perf_table_free(table);
206parse_table_alt(
const char *str)
214 if (l >= 3 && str[0] ==
'F' && str[1] ==
'L')
215 return (FEET2MET(atoi(&str[2]) * 100));
216 if (strcmp(str,
"0") == 0)
226perf_table_extrapolate(
perf_table_t *table,
unsigned col,
227 unsigned last_data_col,
size_t offset)
229 double v1, v2, m1, m2, m;
237 if (last_data_col == 0) {
242 cell1 = &table->rows[table->num_alts - 1][last_data_col];
243 return (*(
double *)((
void *)(cell1) + offset));
246 cell1 = &table->rows[table->num_alts - 1][last_data_col - 1];
247 cell2 = &table->rows[table->num_alts - 1][last_data_col];
248 v1 = *(
double *)((
void *)(cell1) + offset);
249 m1 = table->wts[last_data_col - 1];
250 v2 = *(
double *)((
void *)(cell2) + offset);
251 m2 = table->wts[last_data_col];
254 return (
fx_lin(m, m1, v1, m2, v2));
258perf_table_cells_populate(
char ** comps,
size_t n_comps,
perf_table_t *table,
259 size_t offset,
double conv_factor)
269 for (
size_t i = 0; i < table->num_wts; i++) {
271 double *p = ((
void *)(cell)) + offset;
274 *p = atof(comps[i]) * conv_factor;
276 *p = perf_table_extrapolate(table, i, n_comps - 1,
283perf_table_parse(FILE *fp, perf_table_set_t *ts,
unsigned num_eng,
284 double ff_corr,
unsigned *line_num)
309 comps =
strsplit(line,
" ", B_TRUE, &n_comps);
310 if (strcmp(comps[0],
"ISA") == 0 && n_comps == 2) {
311 table->isa = atof(comps[1]);
312 }
else if (strcmp(comps[0],
"IAS") == 0 && n_comps == 2) {
313 table->ias = atof(comps[1]);
314 }
else if (strcmp(comps[0],
"KIAS") == 0 && n_comps == 2) {
315 table->ias = KT2MPS(atof(comps[1]));
316 }
else if (strcmp(comps[0],
"MACH") == 0 && n_comps == 2) {
317 table->mach = atof(comps[1]);
318 }
else if (strcmp(comps[0],
"GWLBK") == 0 && n_comps >= 2) {
319 table->num_wts = n_comps - 1;
321 sizeof (*table->wts));
322 for (
size_t i = 1; i < n_comps; i++) {
324 LBS2KG(1000 * atof(comps[i]));
326 }
else if (!isnan(alt = parse_table_alt(comps[0]))) {
328 table->alts = realloc(table->alts, table->num_alts *
329 sizeof (*table->alts));
330 table->alts[table->num_alts - 1] = alt;
331 table->rows = realloc(table->rows, table->num_alts *
332 sizeof (*table->rows));
335 }
else if (strcmp(comps[0],
"FPM") == 0) {
336 perf_table_cells_populate(comps, n_comps, table,
339 }
else if (strcmp(comps[0],
"TIMM") == 0) {
340 perf_table_cells_populate(comps, n_comps, table,
342 }
else if (strcmp(comps[0],
"FULB") == 0) {
343 perf_table_cells_populate(comps, n_comps, table,
345 LBS2KG(1) * ff_corr);
346 }
else if (strcmp(comps[0],
"FFLB/ENG") == 0) {
347 perf_table_cells_populate(comps, n_comps, table,
349 (LBS2KG(1) / SECS_PER_HR) * num_eng * ff_corr);
350 }
else if (strcmp(comps[0],
"ENDTABLE") == 0) {
360 if ((isnan(table->ias) && isnan(table->mach)) || table->num_wts == 0 ||
361 table->num_alts == 0)
373 for (
unsigned i_alt = 0; i_alt < table->num_alts; i_alt++) {
374 for (
unsigned i_wt = 0; i_wt < table->num_wts; i_wt++) {
377 if (cell->fused_t == 0)
380 cell->ff = cell->fused / cell->fused_t;
383 &table->rows[i_alt - 1][i_wt];
384 double fused, fused_t;
388 fused = cell->fused - subcell->fused;
389 fused_t = cell->fused_t - subcell->fused_t;
390 cell->ff = fused / fused_t;
392 ASSERT_MSG(cell->ff >= 0,
"Malformed table with "
393 "negative fuel flow: ISA=%.0f KIAS=%.0f Mach=%.2f "
394 "ALT=%.0fft WT=%.0flbs", table->isa,
395 MPS2KT(table->ias), table->mach,
396 MET2FEET(table->alts[i_alt]),
397 KG2LBS(table->wts[i_wt]));
401 srch_isa.isa = table->isa;
402 isa =
avl_find(&ts->by_isa, &srch_isa, &where);
405 isa->isa = table->isa;
406 avl_create(&isa->by_ias, perf_tables_ias_compar,
408 avl_create(&isa->by_mach, perf_tables_mach_compar,
414 if (!isnan(table->ias)) {
415 if (
avl_find(&isa->by_ias, table, &where) != NULL) {
416 logMsg(
"Duplicate table for ISA %.1f/IAS %.1f",
417 table->isa, MPS2KT(table->ias));
422 if (!isnan(table->mach)) {
423 if (
avl_find(&isa->by_mach, table, &where) != NULL) {
424 logMsg(
"Duplicate table for ISA %.1f/Mach %.3f",
425 table->isa, MPS2KT(table->mach));
433 perf_table_free(table);
442 for (
unsigned i = 0; i < table->num_alts; i++)
443 free(table->rows[i]);
450parse_curve_lin(FILE *fp,
vect2_t **curvep,
size_t numpoints,
456 ssize_t line_len = 0;
460 curve =
safe_calloc(numpoints + 1,
sizeof (*curve));
462 for (
size_t i = 0; i < numpoints; i++) {
470 curve[i] =
VECT2(atof(comps[0]), atof(comps[1]));
471 if (i > 0 && curve[i - 1].x >= curve[i].x)
502 tree = &isa->by_mach;
508 t_min =
avl_find(tree, &srch, &where);
515 ASSERT(t_min != NULL || t_max != NULL);
517 if (
AVL_NEXT(tree, t_max) != NULL) {
525 if (
AVL_PREV(tree, t_min) != NULL) {
541perf_tables_find(perf_table_set_t *ts,
542 double isadev,
double spd, bool_t is_mach,
559 isa0 =
avl_find(&ts->by_isa, &srch, &where);
570 ASSERT(isa0 != NULL || isa1 != NULL);
576 if (
AVL_NEXT(&ts->by_isa, isa1) != NULL) {
584 if (
AVL_PREV(&ts->by_isa, isa0) != NULL) {
595 perf_tables_find_spd(isa0, spd, is_mach, isa0_min, isa0_max);
596 perf_tables_find_spd(isa1, spd, is_mach, isa1_min, isa1_max);
603 double mass,
size_t field_offset)
605 unsigned col1 = 0, col2 = 1;
609 ASSERT(table->num_wts != 0);
614 mass =
clamp(mass, table->wts[0], table->wts[table->num_wts - 1] - 1);
616 for (
unsigned i = 0; i + 1 < table->num_wts; i++) {
617 if (mass >= table->wts[i] && mass <= table->wts[i + 1]) {
624 v1 = *(
double *)((
void *)(&row[col1]) + field_offset);
625 v2 = *(
double *)((
void *)(&row[col2]) + field_offset);
628 v =
fx_lin(mass, table->wts[col1], v1, table->wts[col2], v2);
637perf_table_lookup(
perf_table_t *table,
double mass,
double alt,
640 unsigned row1 = UINT32_MAX, row2 = UINT32_MAX;
641 double row1_val, row2_val, value;
651 if (alt > table->alts[0]) {
654 }
else if (alt < table->alts[table->num_alts - 1]) {
655 row1 = table->num_alts - 2;
656 row2 = table->num_alts - 1;
658 for (
unsigned i = 0; i + 1 < table->num_alts; i++) {
659 if (alt <= table->alts[i] &&
660 alt >= table->alts[i + 1]) {
670 row1_val = perf_table_lookup_row(table, table->rows[row1], mass,
672 row2_val = perf_table_lookup_row(table, table->rows[row2], mass,
674 value =
fx_lin(alt, table->alts[row1], row1_val, table->alts[row2],
682table_lookup_common(perf_table_set_t *ts,
double isadev,
double mass,
683 double spd_mps_or_mach, bool_t is_mach,
double alt,
size_t offset)
687 double isa0_min_param, isa0_max_param, isa1_min_param, isa1_max_param;
688 double isa0_param, isa1_param, param;
693 perf_tables_find(ts, isadev, spd_mps_or_mach, is_mach,
694 &isa0_min, &isa0_max, &isa1_min, &isa1_max);
696 if (isa0_min != isa0_max) {
697 double x0 = (is_mach ? isa0_min->mach : isa0_min->ias);
698 double x1 = (is_mach ? isa0_max->mach : isa0_max->ias);
699 double rat =
iter_fract(spd_mps_or_mach, x0, x1, B_FALSE);
700 isa0_min_param = perf_table_lookup(isa0_min, mass, alt, offset);
701 isa0_max_param = perf_table_lookup(isa0_max, mass, alt, offset);
707 isa0_param =
wavg2(isa0_min_param, isa0_max_param,
708 clamp(rat, -0.25, 2));
709 ASSERT(!isnan(isa0_param));
711 isa0_param = perf_table_lookup(isa0_min, mass, alt, offset);
713 if (isa1_min != isa1_max) {
714 double x0 = (is_mach ? isa1_min->mach : isa1_min->ias);
715 double x1 = (is_mach ? isa1_max->mach : isa1_max->ias);
716 double rat =
iter_fract(spd_mps_or_mach, x0, x1, B_FALSE);
717 isa1_min_param = perf_table_lookup(isa1_min, mass, alt, offset);
718 isa1_max_param = perf_table_lookup(isa1_max, mass, alt, offset);
719 isa1_param =
wavg2(isa1_min_param, isa1_max_param,
720 clamp(rat, -0.25, 2));
721 ASSERT(!isnan(isa1_param));
723 isa1_param = perf_table_lookup(isa1_min, mass, alt, offset);
726 if (isa0_param != isa1_param) {
728 isa1_min->isa, B_FALSE), -0.5, 1.5);
729 param =
wavg2(isa0_param, isa1_param, rat);
737#define PARSE_SCALAR(name, var) \
738 if (strcmp(comps[0], name) == 0) { \
740 logMsg("Error parsing acft perf file %s:%d: " \
741 "malformed or duplicate " name " line.", \
742 filename, line_num); \
745 (var) = atof(comps[1]); \
746 if ((var) <= 0.0) { \
747 logMsg("Error parsing acft perf file %s:%d: " \
748 "invalid value for " name, filename, line_num); \
758#define PARSE_CURVE(name, var) \
759 if (strcmp(comps[0], name) == 0) { \
760 if (ncomps != 2 || atoi(comps[1]) < 2 || (var) != NULL) { \
761 logMsg("Error parsing acft perf file %s:%d: " \
762 "malformed or duplicate " name " line.", \
763 filename, line_num); \
766 if (!parse_curve_lin(fp, &(var), atoi(comps[1]), &line_num)) { \
767 logMsg("Error parsing acft perf file %s:%d: " \
768 "malformed or missing lines.", filename, \
774#define PARSE_TABLE(name, table_set) \
775 if (strcmp(comps[0], (name)) == 0) { \
776 if ((table_set) == NULL) \
777 (table_set) = perf_table_set_alloc(); \
778 if (!perf_table_parse(fp, (table_set), acft->num_eng, \
779 table_ff_corr, &line_num)) { \
780 logMsg("Error parsing acft perf file %s:%d: " \
781 "malformed or missing lines.", filename, \
788acft_perf_parse(
const char *filename)
791 FILE *fp = fopen(filename,
"r");
794 unsigned line_num = 0;
795 ssize_t line_len = 0;
796 char *comps[MAX_LINE_COMPS];
797 bool_t version_check_completed = B_FALSE;
798 double table_ff_corr = 1;
803 for (
int i = 0; i < FLT_PERF_NUM_SPD_LIMS; i++) {
813 ncomps =
explode_line(line,
',', comps, MAX_LINE_COMPS);
815 logMsg(
"Error parsing acft perf file %s:%d: "
816 "malformed line, too many line components.",
821 if (strcmp(comps[0],
"VERSION") == 0) {
824 if (version_check_completed) {
825 logMsg(
"Error parsing acft perf file %s:%d: "
826 "duplicate VERSION line.", filename,
831 logMsg(
"Error parsing acft perf file %s:%d: "
832 "malformed VERSION line.", filename,
836 vers = atoi(comps[1]);
837 if (vers < ACFT_PERF_MIN_VERSION ||
838 vers > ACFT_PERF_MAX_VERSION) {
839 logMsg(
"Error parsing acft perf file %s:%d: "
840 "unsupported file version %d.", filename,
844 version_check_completed = B_TRUE;
847 if (!version_check_completed) {
848 logMsg(
"Error parsing acft perf file %s:%d: first "
849 "line was not VERSION.", filename, line_num);
852 if (strcmp(comps[0],
"ACFTTYPE") == 0) {
853 if (ncomps != 2 || acft->acft_type != NULL) {
854 logMsg(
"Error parsing acft perf file %s:%d: "
855 "malformed or duplicate ACFTTYPE line.",
859 acft->acft_type = strdup(comps[1]);
860 }
else if (strcmp(comps[0],
"ENGTYPE") == 0) {
861 if (ncomps != 2 || acft->eng_type != NULL) {
862 logMsg(
"Error parsing acft perf file %s:%d: "
863 "malformed or duplicate ENGTYPE line.",
867 acft->eng_type = strdup(comps[1]);
869 else PARSE_SCALAR(
"NUMENG", acft->num_eng)
870 else PARSE_SCALAR("MAXTHR", acft->eng_max_thr)
871 else PARSE_SCALAR("MINTHR", acft->eng_min_thr)
872 else PARSE_SCALAR("SFC", acft->eng_sfc)
873 else PARSE_SCALAR("REFZFW", acft->ref.zfw)
874 else PARSE_SCALAR("REFFUEL", acft->ref.fuel)
875 else PARSE_SCALAR("REFCRZLVL", acft->ref.crz_lvl)
876 else PARSE_SCALAR("REFCLBIAS", acft->ref.clb_ias)
877 else PARSE_SCALAR("REFCLBIASINIT", acft->ref.clb_ias_init)
878 else PARSE_SCALAR("REFCLBMACH", acft->ref.clb_mach)
879 else PARSE_SCALAR("REFCRZIAS", acft->ref.crz_ias)
880 else PARSE_SCALAR("REFCRZMACH", acft->ref.crz_mach)
881 else PARSE_SCALAR("REFDESIAS", acft->ref.des_ias)
882 else PARSE_SCALAR("REFDESMACH", acft->ref.des_mach)
883 else PARSE_SCALAR("REFTOFLAP", acft->ref.to_flap)
884 else PARSE_SCALAR("REFACCELHT", acft->ref.accel_hgt)
885 else PARSE_SCALAR("REFCLBSPDLIM[0]",
886 acft->ref.clb_spd_lim[0].kias)
887 else PARSE_SCALAR("REFCLBSPDLIMALT[0]",
888 acft->ref.clb_spd_lim[0].alt_ft)
889 else PARSE_SCALAR("REFCLBSPDLIM[1]",
890 acft->ref.clb_spd_lim[1].kias)
891 else PARSE_SCALAR("REFCLBSPDLIMALT[1]",
892 acft->ref.clb_spd_lim[1].alt_ft)
893 else PARSE_SCALAR("REFDESSPDLIM[0]",
894 acft->ref.des_spd_lim[0].kias)
895 else PARSE_SCALAR("REFDESSPDLIMALT[0]",
896 acft->ref.des_spd_lim[0].alt_ft)
897 else PARSE_SCALAR("REFDESSPDLIM[1]",
898 acft->ref.des_spd_lim[1].kias)
899 else PARSE_SCALAR("REFDESSPDLIMALT[1]",
900 acft->ref.des_spd_lim[1].alt_ft)
901 else PARSE_SCALAR("WINGAREA", acft->wing_area)
902 else PARSE_SCALAR("CLMAX", acft->cl_max_aoa)
903 else PARSE_SCALAR("CLFLAPMAX", acft->cl_flap_max_aoa)
904 else PARSE_SCALAR("TABLEFFCORR", table_ff_corr)
905 else PARSE_CURVE("THRDENS", acft->thr_dens_curve)
906 else PARSE_CURVE("THRMACH", acft->thr_mach_curve)
907 else PARSE_CURVE("SFCTHRO", acft->sfc_thro_curve)
908 else PARSE_CURVE("SFCISA", acft->sfc_isa_curve)
909 else PARSE_CURVE("CL", acft->cl_curve)
910 else PARSE_CURVE("CLFLAP", acft->cl_flap_curve)
911 else PARSE_CURVE("CD", acft->cd_curve)
912 else PARSE_CURVE("CDFLAP", acft->cd_flap_curve)
913 else PARSE_CURVE("HALFBANK", acft->half_bank_curve)
914 else PARSE_CURVE("FULLBANK", acft->full_bank_curve)
915 else PARSE_TABLE("CLBTABLE", acft->clb_tables)
916 else PARSE_TABLE("CRZTABLE", acft->crz_tables)
917 else PARSE_TABLE("DESTABLE", acft->des_tables)
919 logMsg(
"Error parsing acft perf file %s:%d: unknown "
920 "line", filename, line_num);
925 if (acft->acft_type == NULL ||
926 acft->ref.clb_ias <= 0 ||
927 acft->ref.clb_ias_init <= 0 ||
928 acft->ref.clb_mach <= 0 ||
929 acft->ref.crz_ias <= 0 ||
930 acft->ref.crz_mach <= 0 ||
931 acft->ref.des_ias <= 0 ||
932 acft->ref.des_mach <= 0 ||
933 acft->eng_type == NULL ||
934 acft->eng_max_thr <= 0 ||
935 acft->eng_min_thr <= 0 ||
936 acft->eng_sfc <= 0 ||
937 acft->num_eng <= 0 ||
938 acft->thr_mach_curve == NULL ||
939 acft->sfc_thro_curve == NULL ||
940 acft->sfc_isa_curve == NULL ||
941 acft->cl_curve == NULL ||
942 acft->cl_flap_curve == NULL ||
943 acft->cd_curve == NULL ||
944 acft->cd_flap_curve == NULL ||
945 acft->wing_area == 0 ||
946 acft->half_bank_curve == NULL ||
947 acft->full_bank_curve == NULL) {
948 logMsg(
"Error parsing acft perf file %s: missing or corrupt "
949 "data fields.", filename);
956 acft->ref.thr_derate = 1;
963 acft_perf_destroy(acft);
972 free(acft->acft_type);
974 free(acft->eng_type);
975 free(acft->thr_dens_curve);
976 free(acft->thr_mach_curve);
977 free(acft->sfc_thro_curve);
978 free(acft->sfc_isa_curve);
979 free(acft->cl_curve);
980 free(acft->cl_flap_curve);
981 free(acft->cd_curve);
982 free(acft->cd_flap_curve);
983 free(acft->half_bank_curve);
984 free(acft->full_bank_curve);
985 if (acft->clb_tables != NULL)
986 perf_table_set_free(acft->clb_tables);
987 if (acft->crz_tables != NULL)
988 perf_table_set_free(acft->crz_tables);
989 if (acft->des_tables != NULL)
990 perf_table_set_free(acft->des_tables);
998 memcpy(flt, &acft->ref, sizeof (*flt));
1013 if (flt->num_eng > 0 && flt->num_eng <= acft->num_eng)
1014 return (flt->num_eng);
1015 return (acft->num_eng);
1037 double alt,
double ktas,
double qnh,
double isadev,
double tp_alt)
1039 double Ps, D, dmod, mmod, mach, sat;
1041 double min_thr, max_thr;
1048 num_eng = get_num_eng(flt, acft);
1050 Ps = alt2press(alt, qnh);
1051 sat = isadev2sat(alt2fl(alt < tp_alt ? alt : tp_alt, qnh), isadev);
1052 D = air_density(Ps, isadev);
1053 dmod = D / ISA_SL_DENS;
1054 if (acft->thr_dens_curve != NULL)
1055 dmod *=
fx_lin_multi(dmod, acft->thr_dens_curve, B_TRUE);
1056 mach = ktas2mach(ktas, sat);
1057 mmod =
fx_lin_multi(mach, acft->thr_mach_curve, B_TRUE);
1059 max_thr = num_eng * acft->eng_max_thr * dmod * mmod * flt->thr_derate;
1060 min_thr = num_eng * acft->eng_min_thr * dmod * mmod * flt->thr_derate;
1062 return (
wavg(min_thr, max_thr, throttle));
1070 return (get_num_eng(flt, acft) * acft->eng_min_thr);
1090 double alt2,
double ktas,
double qnh,
double isadev,
double tp_alt)
1092 double Ps, D, dmod, mmod, avg_temp, thr;
1093 double avg_alt = AVG(alt1, alt2);
1095 double alt1_fl = alt2fl(alt1, qnh);
1096 double alt2_fl = alt2fl(alt2, qnh);
1097 double tp_fl = alt2fl(tp_alt, qnh);
1098 unsigned num_eng = get_num_eng(flt, acft);
1104 avg_temp = AVG(isadev2sat(alt1_fl, isadev),
1105 isadev2sat(alt2_fl < tp_fl ? alt2_fl : tp_fl, isadev));
1107 mach = ktas2mach(ktas, avg_temp);
1108 mmod =
fx_lin_multi(mach, acft->thr_mach_curve, B_TRUE);
1110 Ps = alt2press(avg_alt, qnh);
1114 isadev = isadev2sat(alt2fl(avg_alt, qnh), avg_temp);
1115 D = air_density(Ps, isadev);
1116 dmod = D / ISA_SL_DENS;
1117 if (acft->thr_dens_curve != NULL)
1118 dmod *=
fx_lin_multi(dmod, acft->thr_dens_curve, B_TRUE);
1122 thr = num_eng * dmod * mmod * flt->thr_derate;
1139cl_curve_get_aoa(
double Cl,
const vect2_t *curve)
1142 double *candidates = NULL;
1148 if (n == 0 || n == SIZE_MAX) {
1153 aoa = candidates[0];
1154 for (
size_t i = 1; i < n; i++) {
1155 if (aoa > candidates[i]) {
1156 ASSERT(!isnan(candidates[i]));
1157 aoa = candidates[i];
1176calc_total_E(
double mass,
double altm,
double tas)
1178 return (mass * EARTH_GRAVITY * altm + 0.5 * mass *
POW2(tas));
1193total_E_to_alt(
double E,
double m,
double tas)
1195 return ((E - (0.5 * m *
POW2(tas))) / (m * EARTH_GRAVITY));
1213get_aoa(
double Pd,
double mass,
double flap_ratio,
const acft_perf_t *acft)
1217 lift = MASS2GFORCE(mass);
1218 Cl = lift / (Pd * acft->wing_area);
1219 if (flap_ratio == 0) {
1220 return (cl_curve_get_aoa(Cl, acft->cl_curve));
1222 double aoa_no_flap = cl_curve_get_aoa(Cl, acft->cl_curve);
1223 double aoa_flap = cl_curve_get_aoa(Cl, acft->cl_flap_curve);
1225 return (
wavg(aoa_no_flap, aoa_flap, flap_ratio));
1241get_drag(
double Pd,
double aoa,
double flap_ratio,
const acft_perf_t *acft)
1243 if (flap_ratio == 0)
1244 return (
fx_lin_multi(aoa, acft->cd_curve, B_TRUE) * Pd *
1249 flap_ratio) * Pd * acft->wing_area);
1301spd_chg_step(bool_t accel,
double isadev,
double tp_alt,
double qnh, bool_t gnd,
1302 double alt,
double *kcasp,
double kcas_targ,
double wind_mps,
double mass,
1304 double *distp,
double *timep,
double *burnp)
1306 double aoa, drag, delta_v, E_now, E_lim, E_targ, tas_lim;
1307 double fl = alt2fl(alt, qnh);
1308 double Ps = alt2press(alt, qnh);
1309 double oat = isadev2sat(fl, isadev);
1311 double ktas_now = kcas2ktas(*kcasp, Ps, oat);
1312 double tas_now = KT2MPS(ktas_now);
1313 double tas_targ = KT2MPS(kcas2ktas(kcas_targ, Ps, oat));
1314 double Pd = dyn_press(ktas_now, Ps, oat);
1315 double throttle = accel ? 1.0 : 0.0;
1316 double thr = eng_get_thrust(flt, acft, throttle, alt, ktas_now, qnh,
1318 double burn = *burnp;
1320 double altm = FEET2MET(alt);
1325 aoa = get_aoa(Pd, mass, flap_ratio, acft);
1328 drag = get_drag(Pd, aoa, flap_ratio, acft);
1330 delta_v = MAX((thr - drag) / mass, 0);
1332 tas_lim = tas_now + delta_v * t;
1333 E_now = calc_total_E(mass, altm, tas_now);
1334 E_lim = calc_total_E(mass, altm, tas_lim);
1335 E_targ = calc_total_E(mass, altm, tas_targ);
1337 if (accel ? E_targ > E_lim : E_targ < E_lim) {
1338 *kcasp = ktas2kcas(MPS2KT(tas_lim), Ps, oat);
1340 t *= ((E_targ - E_now) / (E_lim - E_now));
1341 *kcasp = ktas2kcas(MPS2KT(tas_targ), Ps, oat);
1346 burn += acft_get_sfc(flt, acft, thr, alt, ktas_now, qnh,
1347 isadev, tp_alt) * (t / SECS_PER_HR);
1351 double dist = MET2NM(tas_now * t + 0.5 * delta_v *
POW2(t) +
1353 (*distp) += MAX(dist, 0);
1357clb_table_step(
const acft_perf_t *acft,
double isadev,
double qnh,
double alt,
1358 double spd, bool_t is_mach,
double mass,
double wind_mps,
double d_t,
1359 double *nalt,
double *nburn,
double *ndist)
1362 double kcas, ktas_now, tas_now;
1363 double fl = alt2fl(alt, qnh);
1364 double Ps = alt2press(alt, qnh);
1365 double oat = isadev2sat(fl, isadev);
1368 ASSERT(acft->clb_tables != NULL);
1374 ff = table_lookup_common(acft->clb_tables, isadev, mass, spd, is_mach,
1377 vs = table_lookup_common(acft->clb_tables, isadev, mass, spd, is_mach,
1382 ktas_now = mach2ktas(spd, oat);
1383 kcas = ktas2kcas(ktas_now, Ps, oat);
1386 ktas_now = kcas2ktas(kcas, Ps, oat);
1388 tas_now = KT2MPS(ktas_now);
1390 *nalt = alt + vs * d_t;
1392 *ndist = MAX(tas_now + wind_mps, 0) * d_t;
1396crz_step(
double isadev,
double tp_alt,
double qnh,
double alt_ft,
1397 double spd_mps_or_mach, bool_t is_mach,
double wind_mps,
double mass,
1399 double d_t,
double *distp,
double *burnp,
double *ttg_out)
1401 double burn, kcas, ktas_now, tas_now, burn_step, dist_step;
1402 double fl = alt2fl(alt_ft, qnh);
1403 double Ps = alt2press(alt_ft, qnh);
1404 double oat = isadev2sat(fl, isadev);
1415 ktas_now = mach2ktas(spd_mps_or_mach, oat);
1416 kcas = ktas2kcas(ktas_now, Ps, oat);
1418 kcas = MPS2KT(spd_mps_or_mach);
1419 ktas_now = kcas2ktas(kcas, Ps, oat);
1421 tas_now = KT2MPS(ktas_now);
1423 if (acft->crz_tables != NULL) {
1424 double ff = table_lookup_common(acft->crz_tables, isadev, mass,
1425 spd_mps_or_mach, is_mach, FEET2MET(alt_ft),
1428 burn_step = ff * d_t;
1430 double spd_kias_or_mach = (is_mach ? spd_mps_or_mach :
1431 MPS2KT(spd_mps_or_mach));
1432 printf(
"CRZ:%5.0f ft m:%5.0f spd:%.*f lb ff:%4.0f "
1433 "lb/hr/eng\n", alt_ft, KG2LBS(mass),
1434 is_mach ? 3 : 0, spd_kias_or_mach,
1435 KG2LBS(ff) * SECS_PER_HR / get_num_eng(flt, acft));
1438 double aoa, drag, thr, sfc, Pd;
1440 Pd = dyn_press(ktas_now, Ps, oat);
1441 aoa = get_aoa(Pd, mass, 0, acft);
1443 drag = get_drag(Pd, aoa, 0, acft);
1445 sfc = acft_get_sfc(flt, acft, thr, alt_ft, ktas_now, qnh,
1447 printf(
"Ps: %.0f Pd: %.0f kcas: %.0f aoa: %.3f drag: %.2f "
1448 "sfc: %.1f gw: %.1f\n", Ps, Pd, kcas, aoa, drag / 1000,
1449 KG2LBS(sfc) / get_num_eng(flt, acft), KG2LBS(mass) / 1000);
1450 burn_step = sfc * (d_t / SECS_PER_HR);
1456 dist_step = MAX(tas_now + wind_mps, KT2MPS(60)) * d_t;
1457 if ((*distp) + MET2NM(dist_step) > dist_nm) {
1458 double rat = (dist_nm - (*distp)) / MET2NM(dist_step);
1460 dist_step = NM2MET(dist_nm - (*distp));
1461 if (ttg_out != NULL)
1462 (*ttg_out) += d_t * rat;
1464 if (ttg_out != NULL)
1469 (*distp) += MET2NM(dist_step);
1498alt_chg_step(bool_t clb,
double isadev,
double tp_alt,
double qnh,
1499 double *altp,
double *vsp,
double *kcasp,
double alt_targ,
double wind_mps,
1500 double mass,
double flap_ratio,
const acft_perf_t *acft,
1501 const flt_perf_t *flt,
double *distp,
double *timep,
double *burnp)
1503 double aoa, drag, E_now, E_lim, E_targ;
1505 double fl = alt2fl(alt, qnh);
1506 double Ps = alt2press(alt, qnh);
1507 double oat = isadev2sat(fl, isadev);
1509 double ktas_now = kcas2ktas(*kcasp, Ps, oat);
1510 double tas_now = KT2MPS(ktas_now);
1511 double Pd = dyn_press(ktas_now, Ps, oat);
1512 double throttle = clb ? 1.0 : 0.0;
1513 double thr = eng_get_thrust(flt, acft, throttle, alt, ktas_now, qnh,
1515 double burn = *burnp;
1517 double altm = FEET2MET(alt);
1519 aoa = get_aoa(Pd, mass, flap_ratio, acft);
1521 drag = get_drag(Pd, aoa, flap_ratio, acft);
1524 thr = MAX(thr, drag);
1526 thr = MIN(thr, drag);
1529 E_now = calc_total_E(mass, altm, tas_now);
1530 E_lim = E_now + (thr - drag) * tas_now * t;
1531 E_targ = calc_total_E(mass, FEET2MET(alt_targ), tas_now);
1533 if (clb ? E_targ > E_lim : E_targ < E_lim) {
1534 double nalt = total_E_to_alt(E_lim, mass, tas_now);
1535 double vs_tgt = (nalt - FEET2MET(*altp)) / t;
1536 double v_accel = (vs_tgt - (*vsp)) / t;
1538 v_accel =
clamp(v_accel, -2.5, 2.5);
1539 vs_tgt = (*vsp) + (v_accel * t);
1540 *altp = MET2FEET(FEET2MET(*altp) + vs_tgt * t);
1543 t *= ((E_targ - E_now) / (E_lim - E_now));
1549 Ps = alt2press(*altp, qnh);
1550 fl = alt2fl(*altp, qnh);
1551 oat = isadev2sat(fl, isadev);
1552 *kcasp = ktas2kcas(ktas_now, Ps, oat);
1555 burn += acft_get_sfc(flt, acft, thr, alt, ktas_now, qnh, isadev,
1556 tp_alt) * (t / SECS_PER_HR);
1559 double dist = MET2NM(sqrt(
POW2(tas_now * t) +
1560 FEET2MET(
POW2((*altp) - alt))) + wind_mps * t);
1561 (*distp) += MAX(dist, 0);
1565des_burn_step(
double isadev,
double alt_m,
double vs_act_mps,
1566 double spd_mps_or_mach, bool_t is_mach,
double mass,
1569 double ff_des = table_lookup_common(acft->des_tables, isadev, mass,
1571 double ff_crz = table_lookup_common(acft->crz_tables, isadev, mass,
1573 double vs_des_mps = table_lookup_common(acft->des_tables, isadev, mass,
1575 double rat =
iter_fract(vs_act_mps, 0, vs_des_mps, B_TRUE);
1580 ff_des = MAX(ff_des, 0);
1581 ff_crz = MAX(ff_crz, 0);
1582 burn =
wavg(ff_crz, ff_des, rat) * d_t;
1584 printf(
"DES:%-5.0f ft m:%-5.0f lb vs:%-5.0f fpm "
1585 "ff_crz:%-4.0f lbs/hr ff_des:%-4.0f rat:%.3f\n",
1586 MET2FEET(alt_m), KG2LBS(mass), MPS2FPM(vs_des_mps),
1587 KG2LBS(ff_crz) * SECS_PER_HR, KG2LBS(ff_des) * SECS_PER_HR,
1604accel_time_split(accelclb_t type,
double kcas,
double clbias,
double alt,
1605 double accel_alt,
double t,
double flap_ratio,
double flap_ratio_takeoff,
1606 double *flap_ratio_act)
1608 if (flap_ratio_act != NULL)
1609 *flap_ratio_act = flap_ratio;
1612 case ACCEL_THEN_CLB:
1615 if (kcas < clbias) {
1616 if (flap_ratio_act != NULL)
1617 *flap_ratio_act = flap_ratio_takeoff;
1620 if (alt < accel_alt) {
1621 if (flap_ratio_act != NULL)
1622 *flap_ratio_act = flap_ratio_takeoff;
1633select_step(accelclb_t type)
1635 if (type == ACCEL_TAKEOFF)
1636 return (SECS_PER_STEP_TAKEOFF);
1638 return (SECS_PER_STEP);
1642should_use_clb_tables(
const acft_perf_t *acft, accelclb_t type,
1643 double kcas,
double kcas_lim)
1645 return (acft->clb_tables != NULL &&
1646 (type == ACCEL_AND_CLB || type == ACCEL_THEN_CLB ||
1647 kcas_lim - kcas < KCAS_TABLE_THRESH));
1688 double qnh,
double tp_alt,
double accel_alt,
double fuel,
vect2_t dir,
1689 double alt1_ft,
double kcas1,
vect2_t wind1,
1690 double alt2_ft,
double kcas2,
vect2_t wind2,
1691 double flap_ratio,
double mach_lim, accelclb_t type,
double *burnp,
1694 double alt = alt1_ft, kcas = kcas1, burn = 0, dist = 0;
1695 double step = select_step(type);
1696 double flap_ratio_act;
1697 int iter_counter = 0;
1702 ASSERT(!isnan(accel_alt) || type != ACCEL_TAKEOFF);
1706 while (alt2_ft - alt > ALT_THRESH && kcas2 - kcas > KCAS_THRESH) {
1707 double wind_mps, alt_fract, accel_t, clb_t, ktas_lim_mach,
1708 kcas_lim_mach, oat, kcas_lim;
1712 double old_alt = alt;
1713 double old_kcas = kcas;
1714 bool_t table = B_FALSE;
1716 ASSERT3S(iter_counter, <, MAX_ITER_STEPS);
1718 oat = isadev2sat(alt2fl(alt, qnh), isadev);
1719 Ps = alt2press(alt, qnh);
1720 ktas_lim_mach = mach2ktas(mach_lim, oat);
1721 kcas_lim_mach = ktas2kcas(ktas_lim_mach, Ps, oat);
1724 for (
int i = 0; i < FLT_PERF_NUM_SPD_LIMS; i++) {
1725 if (alt < flt->clb_spd_lim[i].alt_ft) {
1726 kcas_lim = MIN(kcas_lim,
1727 flt->clb_spd_lim[i].kias);
1730 if (kcas_lim > kcas_lim_mach)
1731 kcas_lim = kcas_lim_mach;
1732 if (alt2_ft - alt < ALT_THRESH && kcas_lim < kcas2)
1739 alt_fract = (alt - alt1_ft) / (alt2_ft - alt1_ft);
1740 wind =
VECT2(
wavg(wind1.x, wind2.x, alt_fract),
1741 wavg(wind1.y, wind2.y, alt_fract));
1747 if (type == ACCEL_TAKEOFF && alt > accel_alt + 1000)
1748 type = ACCEL_AND_CLB;
1750 accel_t = accel_time_split(type, kcas, flt->clb_ias_init,
1751 alt, accel_alt, step, flap_ratio, flt->to_flap,
1763 if (should_use_clb_tables(acft, type, kcas, kcas_lim)) {
1764 bool_t is_mach = (kcas2 > kcas_lim_mach);
1765 double spd = (is_mach ? kcas_lim_mach : KT2MPS(kcas2));
1766 double nalt, nburn, ndist;
1768 clb_table_step(acft, isadev, qnh, FEET2MET(alt),
1769 spd, is_mach, flt->zfw + fuel - burn, wind_mps,
1770 step, &nalt, &nburn, &ndist);
1771 alt = MET2FEET(nalt);
1773 dist += MET2NM(ndist);
1774 clb_t = step - accel_t;
1776 kcas = kcas_lim_mach;
1781 spd_chg_step(B_TRUE, isadev, tp_alt,
1782 qnh, type == ACCEL_TAKEOFF &&
1783 alt == alt1_ft, alt, &kcas, kcas_lim,
1784 wind_mps, flt->zfw + fuel - burn,
1785 flap_ratio_act, acft, flt, &dist,
1789 clb_t = step - accel_t;
1790 if (clb_t > 0 && alt2_ft - alt > ALT_THRESH) {
1791 alt_chg_step(B_TRUE, isadev, tp_alt, qnh,
1792 &alt, &vs, &kcas, alt2_ft, wind_mps,
1793 flt->zfw + fuel - burn, flap_ratio_act,
1794 acft, flt, &dist, &clb_t, &burn);
1801 total_t = accel_t + clb_t;
1802 oat = isadev2sat(alt2fl(alt, qnh), isadev);
1804 printf(
"V:%3.0f KT +V:%5.02lf H:%5.0lf fpm:%4.0lf "
1805 "s:%6.0lf M:%5.03lf tab:%d\n", kcas,
1806 (kcas - old_kcas) / total_t, alt,
1807 ((alt - old_alt) / total_t) * 60, NM2MET(dist),
1808 ktas2mach(kcas2ktas(kcas, alt2press(alt, qnh), oat),
1816 if (kcas_out != NULL)
1825 double isadev,
double qnh,
double tp_alt,
double accel_alt,
1826 double fuel,
vect2_t dir,
double flap_ratio,
double REQ_PTR(alt_ft_p),
1827 double REQ_PTR(kcas_p),
vect2_t wind,
double alt_tgt_ft,
double kcas_tgt,
1828 double mach_lim,
double dist_tgt, accelclb_t type,
double *burnp,
1831 ASSERT3F(*alt_ft_p, <=, alt_tgt_ft);
1832 double alt_ft = *alt_ft_p;
1833 double alt1_ft = alt_ft;
1834 double dist = 0, burn = 0;
1836 double step = select_step(type);
1837 double flap_ratio_act;
1838 int iter_counter = 0;
1843 double kcas = *kcas_p;
1844 ASSERT(!isnan(accel_alt) || type != ACCEL_TAKEOFF);
1853 double oat_guess = isadev2sat(alt2fl(alt_tgt_ft,
1855 double pressure_guess = alt2press(alt_tgt_ft, ISA_SL_PRESS);
1856 double ktas_guess = kcas2ktas(kcas_tgt, pressure_guess,
1858 double min_step = ((dist_tgt / ktas_guess) * 3600) /
1860 step = MAX(step, min_step * 2);
1862 while (dist < dist_tgt && alt_tgt_ft - alt_ft > ALT_THRESH) {
1863 double tas_mps = KT2MPS(kcas2ktas(kcas, alt2press(alt_ft, qnh),
1864 isadev2sat(alt2fl(alt_ft, qnh), isadev)));
1865 double rmng = NM2MET(dist_tgt - dist);
1867 double t_rmng = MIN(rmng / tas_mps, step);
1868 double accel_t, clb_t, oat, Ps, ktas_lim_mach, kcas_lim_mach,
1871 double old_alt = alt_ft;
1872 double old_kcas = kcas;
1873 bool_t table = B_FALSE;
1875 if (iter_counter >= MAX_ITER_STEPS) {
1877 return (
NONE(
double));
1879 oat = isadev2sat(alt2fl(alt_ft, qnh), isadev);
1880 Ps = alt2press(alt_ft, qnh);
1881 ktas_lim_mach = mach2ktas(mach_lim, oat);
1882 kcas_lim_mach = ktas2kcas(ktas_lim_mach, Ps, oat);
1884 kcas_lim = kcas_tgt;
1885 for (
int i = 0; i < FLT_PERF_NUM_SPD_LIMS; i++) {
1886 if (alt_ft < flt->clb_spd_lim[i].alt_ft) {
1887 kcas_lim = MIN(kcas_lim,
1888 flt->clb_spd_lim[i].kias);
1891 if (kcas_lim > kcas_lim_mach)
1892 kcas_lim = kcas_lim_mach;
1893 if (alt_tgt_ft - alt_ft < ALT_THRESH && kcas_lim < kcas_tgt)
1894 kcas_tgt = kcas_lim;
1899 if (type == ACCEL_TAKEOFF && alt_ft > accel_alt + 1000)
1900 type = ACCEL_AND_CLB;
1905 accel_t = accel_time_split(type, kcas, flt->clb_ias_init,
1906 alt_ft, accel_alt, t_rmng, flap_ratio, flt->to_flap,
1909 if (should_use_clb_tables(acft, type, kcas, kcas_lim)) {
1910 bool_t is_mach = (kcas_tgt >= kcas_lim_mach);
1911 double spd = (is_mach ? mach_lim :
1913 double nalt, nburn, ndist;
1915 clb_table_step(acft, isadev, qnh, FEET2MET(alt_ft),
1916 spd, is_mach, flt->zfw + fuel - burn, wind_mps,
1917 step, &nalt, &nburn, &ndist);
1918 alt_ft = MET2FEET(nalt);
1920 dist += MET2NM(ndist);
1921 clb_t = step - accel_t;
1923 kcas = kcas_lim_mach;
1930 spd_chg_step(B_TRUE, isadev, tp_alt,
1931 qnh, type == ACCEL_TAKEOFF &&
1932 alt_ft == alt1_ft, alt_ft, &kcas, kcas_lim,
1933 wind_mps, flt->zfw + fuel - burn,
1934 flap_ratio_act, acft, flt, &dist,
1938 clb_t = t_rmng - accel_t;
1939 if (clb_t > 0 && alt_tgt_ft - alt_ft > ALT_THRESH) {
1940 alt_chg_step(B_TRUE, isadev, tp_alt, qnh,
1941 &alt_ft, &vs, &kcas, alt_tgt_ft, wind_mps,
1942 flt->zfw + fuel - burn, flap_ratio_act,
1943 acft, flt, &dist, &clb_t, &burn);
1950 total_t = accel_t + clb_t;
1951 oat = isadev2sat(alt2fl(alt_ft, qnh), isadev);
1953 printf(
"V:%5.01lf +V:%5.02lf H:%5.0lf fpm:%4.0lf "
1954 "s:%6.0lf M:%5.03lf tab:%d\n", kcas,
1955 ((kcas) - old_kcas) / total_t, alt_ft,
1956 (((alt_ft) - old_alt) / total_t) * 60,
1957 NM2MET(dist), ktas2mach(kcas2ktas(kcas,
1958 alt2press(alt_ft, qnh), oat), oat), table);
1969 if (ttg_out != NULL)
1975 return (
SOME(dist));
1980 double isadev,
double qnh,
double tp_alt,
double fuel,
1981 double alt,
double kcas1,
double kcas2,
double dist_tgt,
1982 double *kcas_out,
double *burn_out)
1984 double dist = 0, burn = 0;
1985 double step = SECS_PER_STEP_DECEL;
1986 double kcas = kcas1;
1987 double oat = isadev2sat(alt2fl(alt, qnh), isadev);
1989 while (dist < dist_tgt && kcas + KCAS_THRESH > kcas2) {
1991 double old_kcas = kcas;
1994 spd_chg_step(B_FALSE, isadev, tp_alt, qnh, B_FALSE,
1995 alt, &kcas, kcas2, 0, flt->zfw + fuel - burn,
1996 0, acft, flt, &dist, &t, &burn);
1999 mach = ktas2mach(kcas2ktas(kcas, alt2press(alt, qnh),
2001 printf(
"V:%5.01lf +V:%5.02lf H:%5.0lf s:%6.0lf "
2002 "M:%5.03lf\n", kcas, (kcas - old_kcas) / t, alt,
2003 NM2MET(dist), mach);
2007 if (kcas_out != NULL)
2009 if (burn_out != NULL)
2039perf_crz2burn(
double isadev,
double tp_alt,
double qnh,
double alt_ft,
2040 double spd, bool_t is_mach,
double hdg,
vect2_t wind1,
vect2_t wind2,
2059 if (ttg_out != NULL)
2066 for (
double dist_done = 0; dist_done < dist_nm;) {
2067 double rat = dist_done / dist_nm;
2068 double mass = flt->zfw + MAX(fuel - burn, 0);
2070 wavg(wind1.y, wind2.y, rat));
2073 crz_step(isadev, tp_alt, qnh, alt_ft, spd, is_mach,
2074 wind_mps, mass, acft, flt, dist_nm, SECS_PER_STEP_CRZ,
2075 &dist_done, &burn, ttg_out);
2086 double isadev,
double qnh,
double fuel,
double hdgt,
2087 double dist_nm,
double mach_lim,
2088 double alt1_ft,
double kcas1,
vect2_t wind1,
2089 double alt2_ft,
double kcas2,
vect2_t wind2,
2114 if (ttg_out != NULL)
2119 for (
double dist_done = 0; dist_done < NM2MET(dist_nm);) {
2120 double rat = dist_done / NM2MET(dist_nm);
2121 double alt_ft =
wavg(alt1_ft, alt2_ft, rat);
2122 double kcas =
wavg(kcas1, kcas2, rat);
2123 double mass = flt->zfw + MAX(fuel - burn, 0);
2125 wavg(wind1.y, wind2.y, rat));
2127 double p = alt2press(alt_ft, qnh);
2128 double fl = alt2fl(alt_ft, qnh);
2129 double oat = isadev2sat(fl, isadev);
2130 double kcas_lim_mach = mach2kcas(mach_lim, alt_ft, qnh, oat);
2132 double tgt_spd, tas_mps, gs_mps, vs_mps, burn_step;
2133 double spd_mps_or_mach, dist_step;
2135 for (
int i = 0; i < FLT_PERF_NUM_SPD_LIMS; i++) {
2136 if (alt_ft <= flt->des_spd_lim[i].alt_ft)
2137 kcas = MIN(kcas, flt->des_spd_lim[i].kias);
2139 is_mach = (kcas > kcas_lim_mach);
2140 tgt_spd = (is_mach ? mach_lim : kcas);
2143 tas_mps = KT2MPS(kcas2ktas(kcas_lim_mach, p, oat));
2146 tas_mps = KT2MPS(kcas2ktas(kcas, p, oat));
2152 gs_mps = MAX(tas_mps + wind_mps, KT2MPS(60));
2153 vs_mps = (FEET2MET(alt2_ft) - FEET2MET(alt1_ft)) /
2154 (NM2MET(dist_nm) / gs_mps);
2155 spd_mps_or_mach = (is_mach ? tgt_spd : KT2MPS(tgt_spd));
2157 burn_step = des_burn_step(isadev, FEET2MET(alt_ft), vs_mps,
2158 spd_mps_or_mach, is_mach, mass, acft, SECS_PER_STEP);
2160 dist_step = gs_mps * SECS_PER_STEP;
2161 if (dist_done + dist_step > NM2MET(dist_nm)) {
2162 double act_dist_step = NM2MET(dist_nm) - dist_done;
2163 double rat = act_dist_step / dist_step;
2164 burn += burn_step * rat;
2165 dist_done += act_dist_step;
2166 if (ttg_out != NULL)
2167 (*ttg_out) += SECS_PER_STEP * rat;
2171 dist_done += dist_step;
2172 if (ttg_out != NULL)
2173 (*ttg_out) += SECS_PER_STEP;
2182 double mass = flt->zfw + flt->fuel;
2183 double lift = MASS2GFORCE(mass);
2185 fx_lin_multi(acft->cl_flap_max_aoa, acft->cl_flap_curve, B_TRUE),
2187 double Pd = lift / (Cl * acft->wing_area);
2188 double tas = sqrt((2 * Pd) / ISA_SL_DENS);
2189 return (MPS2KT(tas));
2204 double alt,
double ktas,
double qnh,
double isadev,
double tp_alt)
2207 double max_thr_AE, min_thr_AE, throttle;
2213 ff_hr = acft->eng_sfc * thr * SECS_PER_HR;
2214 max_thr_AE = eng_get_thrust(flt, acft, 1.0, alt, ktas, qnh, isadev,
2216 min_thr_AE = eng_get_thrust(flt, acft, 0.0, alt, ktas, qnh, isadev,
2218 throttle =
iter_fract(thr, min_thr_AE, max_thr_AE, B_TRUE);
2226perf_get_turn_rate(
double bank_ratio,
double gs_kts,
const flt_perf_t *flt,
2229 double half_bank_rate, full_bank_rate;
2235 if (bank_ratio == 0) {
2238 ASSERT3F(flt->bank_ratio, <=, 1.0);
2239 bank_ratio = flt->bank_ratio;
2245 half_bank_rate =
fx_lin_multi(gs_kts, acft->half_bank_curve, B_TRUE);
2246 if (bank_ratio <= 0.5)
2247 return (2 * bank_ratio * half_bank_rate);
2248 full_bank_rate =
fx_lin_multi(gs_kts, acft->full_bank_curve, B_TRUE);
2250 return (
wavg(half_bank_rate, full_bank_rate,
2251 clamp(2 * (bank_ratio - 0.5), 0, 1)));
2263ktas2mach(
double ktas,
double oat)
2265 return (KT2MPS(ktas) / speed_sound(oat));
2277mach2ktas(
double mach,
double oat)
2279 return (MPS2KT(mach * speed_sound(oat)));
2292ktas2kcas(
double ktas,
double pressure,
double oat)
2294 return (impact_press2kcas(impact_press(ktas2mach(ktas, oat),
2306impact_press2kcas(
double impact_pressure)
2308 return (MPS2KT(ISA_SPEED_SOUND * sqrt(5 *
2309 (pow(impact_pressure / ISA_SL_PRESS + 1, 0.2857142857) - 1))));
2313kcas2mach(
double kcas,
double alt_ft,
double qnh,
double oat)
2315 double p = alt2press(alt_ft, qnh);
2316 double ktas = kcas2ktas(kcas, p, oat);
2317 return (ktas2mach(ktas, oat));
2320double mach2kcas(
double mach,
double alt_ft,
double qnh,
double oat)
2322 double ktas = mach2ktas(mach, oat);
2323 double p = alt2press(alt_ft, qnh);
2324 return (ktas2kcas(ktas, p, oat));
2337kcas2ktas(
double kcas,
double pressure,
double oat)
2349 qc = ISA_SL_PRESS * (pow((
POW2(KT2MPS(kcas)) / (5 *
2350 POW2(ISA_SPEED_SOUND))) + 1, 3.5) - 1);
2359 mach = sqrt(5 * (pow((qc / pressure) + 1, 0.2857142857142) - 1));
2364 return (mach2ktas(mach, oat));
2377mach2keas(
double mach,
double press)
2379 return (MPS2KT(ISA_SPEED_SOUND * mach * sqrt(press / ISA_SL_PRESS)));
2392keas2mach(
double keas,
double press)
2402 return (KT2MPS(keas) / (ISA_SPEED_SOUND * sqrt(press / ISA_SL_PRESS)));
2414alt2press(
double alt_ft,
double qnh_Pa)
2416 return (alt2press_baro(FEET2MET(alt_ft), qnh_Pa, ISA_SL_TEMP_K,
2429press2alt(
double press_Pa,
double qnh_Pa)
2431 return (MET2FEET(press2alt_baro(press_Pa, qnh_Pa, ISA_SL_TEMP_K,
2436alt2press_baro(
double alt_m,
double p0_Pa,
double T0_K,
double g_mss)
2457 return (p0_Pa * pow(1 - ((ISA_TLR_PER_1M * alt_m) / T0_K),
2458 (g_mss * DRY_AIR_MOL) / (R_univ * ISA_TLR_PER_1M)));
2462press2alt_baro(
double p_Pa,
double p0_Pa,
double T0_K,
double g_mss)
2485 return ((T0_K * (1 - pow(p_Pa / p0_Pa, (R_univ * ISA_TLR_PER_1M) /
2486 (g_mss * DRY_AIR_MOL)))) / ISA_TLR_PER_1M);
2498alt2fl(
double alt_ft,
double qnh)
2500 return (press2alt(alt2press(alt_ft, qnh), ISA_SL_PRESS) / 100);
2512fl2alt(
double fl,
double qnh)
2514 return (press2alt(alt2press(fl * 100, ISA_SL_PRESS), qnh));
2526sat2tat(
double sat,
double mach)
2528 return (KELVIN2C(C2KELVIN(sat) * (1 + ((GAMMA - 1) / 2) *
POW2(mach))));
2540tat2sat(
double tat,
double mach)
2542 return (KELVIN2C(C2KELVIN(tat) / (1 + ((GAMMA - 1) / 2) *
POW2(mach))));
2556sat2isadev(
double fl,
double sat)
2558 fl = MIN(fl, ISA_TP_ALT / 100);
2559 return (sat - (ISA_SL_TEMP_C - ((fl / 10) * ISA_TLR_PER_1000FT)));
2571isadev2sat(
double fl,
double isadev)
2573 fl = MIN(fl, ISA_TP_ALT / 100);
2574 return (isadev + ISA_SL_TEMP_C - ((fl / 10) * ISA_TLR_PER_1000FT));
2581speed_sound(
double oat)
2583 return (speed_sound_gas(C2KELVIN(oat), GAMMA, R_spec));
2594speed_sound_gas(
double T,
double gamma,
double R)
2596 return (sqrt(gamma * R * T));
2608air_density(
double pressure,
double oat)
2610 return (gas_density(pressure, oat, R_spec));
2623gas_density(
double pressure,
double oat,
double gas_const)
2633 return (pressure / (gas_const * C2KELVIN(oat)));
2646impact_press(
double mach,
double pressure)
2655 return (pressure * (pow(1 + 0.2 *
POW2(mach), 3.5) - 1));
2668dyn_press(
double ktas,
double press,
double oat)
2670 return (dyn_gas_press(ktas, press, oat, R_spec));
2678dyn_gas_press(
double ktas,
double press,
double oat,
double gas_const)
2680 double p = (0.5 * gas_density(press, oat, gas_const) *
2681 POW2(KT2MPS(ktas)));
2696static_press(
double rho,
double oat)
2698 return (static_gas_press(rho, oat, R_spec));
2706static_gas_press(
double rho,
double oat,
double gas_const)
2716 return (rho * gas_const * C2KELVIN(oat));
2724adiabatic_heating(
double press_ratio,
double start_temp)
2726 return (adiabatic_heating_gas(press_ratio, start_temp, GAMMA));
2751adiabatic_heating_gas(
double press_ratio,
double start_temp,
double gamma)
2753 return (KELVIN2C(pow(pow(C2KELVIN(start_temp), gamma) /
2754 pow(press_ratio, 1 - gamma), 1 / gamma)));
2758air_kin_visc(
double temp_K)
2761 VECT2(200, 0.753e-5),
2762 VECT2(225, 0.935e-5),
2763 VECT2(250, 1.132e-5),
2764 VECT2(275, 1.343e-5),
2765 VECT2(300, 1.568e-5),
2766 VECT2(325, 1.807e-5),
2767 VECT2(350, 2.056e-5),
2768 VECT2(375, 2.317e-5),
2769 VECT2(400, 2.591e-5),
2777air_reynolds(
double vel,
double chord,
double temp_K)
2782 return ((vel * chord) / air_kin_visc(temp_K));
2786lacf_gamma_air(
double T)
2814lacf_therm_cond_air(
double T)
2824 return (
fx_lin(T, 233.2, 0.0209, 498.15, 0.0398));
2828lacf_therm_cond_aluminum(
double T)
2831 VECT2(C2KELVIN(200), 237),
2832 VECT2(C2KELVIN(273), 236),
2833 VECT2(C2KELVIN(400), 240),
2834 VECT2(C2KELVIN(600), 232),
2835 VECT2(C2KELVIN(800), 220),
2843lacf_therm_cond_glass(
double T)
2862earth_gravity_accurate(
double lat,
double alt)
2879 const double delta_per_m = -0.00000305;
2885 return (
fx_lin_multi(
ABS(lat), lat_curve, B_FALSE) + alt * delta_per_m);
#define ASSERT3P(x, op, y)
#define ASSERT_MSG(x, fmt,...)
#define ASSERT3S(x, op, y)
#define ASSERT3U(x, op, y)
#define ASSERT3F(x, op, y)
#define VERIFY3U(x, op, y)
void * avl_destroy_nodes(avl_tree_t *tree, void **cookie)
#define AVL_PREV(tree, node)
void * avl_nearest(const avl_tree_t *tree, avl_index_t where, int direction)
void avl_insert(avl_tree_t *tree, void *node, avl_index_t where)
void avl_create(avl_tree_t *tree, int(*compar)(const void *, const void *), size_t size, size_t offset)
#define AVL_NEXT(tree, node)
unsigned long avl_numnodes(const avl_tree_t *tree)
void avl_destroy(avl_tree_t *tree)
void * avl_find(const avl_tree_t *tree, const void *node, avl_index_t *where)
void lacf_free(void *buf)
double vect2_dotprod(vect2_t a, vect2_t b)
vect2_t vect2_unit(vect2_t a, double *l)
vect2_t hdg2dir(double truehdg)
char ** strsplit(const char *input, const char *sep, bool_t skip_empty, size_t *num)
ssize_t explode_line(char *line, char delim, char **comps, size_t capacity)
static bool_t is_valid_alt_ft(double alt_ft)
static ssize_t parser_get_next_line(FILE *fp, char **linep, size_t *linecap, unsigned *linenum)
static bool_t is_valid_hdg(double hdg)
void free_strlist(char **comps, size_t num)
void list_destroy(list_t *)
void list_create(list_t *, size_t, size_t)
void * list_remove_head(list_t *)
double * fx_lin_multi_inv(double y, const struct vect2_s *points, size_t *num_out)
double fx_lin(double x, double x1, double y1, double x2, double y2)
static double iter_fract(double x, double min_val, double max_val, bool_t clamp_output)
double fx_lin_multi(double x, const struct vect2_s *points, bool_t extrapolate)
static double wavg2(double x, double y, double w)
static double clamp(double x, double min_val, double max_val)
#define NONE(type_name)
Constructs a new optional value in the NONE state.
#define SOME(expr)
Constructs a new optional value in the SOME state.
static void * safe_calloc(size_t nmemb, size_t size)
An optional type for wrapping a non-NAN double value.