Training in Commercial Pilotage with FTD/FFS - Constant-Rate-and-Speed Climb / Descent, Lead Design for Level Off

羽田空港内某所で Flight Simulator による F/O 訓練。B737-Max で 2.0H。

本日は昨年 12 月の時に指導いただいた K 教官。Yoke の握り方や Elevator Trim Switch の触り方だとかに厳しい。正しく持たないと反応が遅れるし、まねごとではなくプロの方法を教えてもらうのは大変ありがたい。

Constant-Rate-and-Speed Climb / Descent & Climbing / Descending Turn

まずはいつもどおり RJTT RWY 34R から 6,000 ft 240 kt を目指す。5,000 ft まではいつもとは異なる指定 Climbing A/S、5,000 ft で HDG 100 への転針指示とあったものの、指定 HDG になることには A/S + 1kt、Alt ± 0ft, HDG ± 0° でピタッと Roll & Level Off させ、安定していますね~とお褒めいただく。

ここから Constant-Rate-and-Speed Climb / Descent の訓練開始。Level Off は VSI x 10% ft 手前から Power 調整開始、VSI x 5% ft 手前から Pitch 調整開始という前回の助言を活かすと、通常の Level Change はおおむね安定して Level Off でき、これまたお褒めいただく。

ただ一度だけ、Descnet で A/S が + 12 kt まで増速してしまっているのにこのタイミング法にやみくもに従ったため、Level Off がムチャクチャになってしまったことがあり、これを機に「Alt および VSI を Pitch で調整し、その上で出来上がる A/S を Power で調整する」のだと助言をいただく。ん? ということは Pitch → Power という操作順になり、前回の現役パイロット教官の助言と 180° 真逆の助言だぞと。しかし、おそらく前提条件の違いによるもので、どちらも間違っていない。その証拠に前回は Power を先、Picth を後にすることにより安定した Level Off になるということは理解できたが、うまくハマったのは基本の時だけで、A/S が目標から乖離しているときに開始点や操作スピードをどれだけ調整すればよいかは設計できなかった。きっと、操作タイミングの前後関係については前回の助言、操作量については今回の助言を取り入れ、体系立てて理論化すれば技量が飛躍的に向上するはず *1

応用編ということで訓練中盤は Turn を加えて Climbing / Descending Turn へ移行する。Turn を加えると、安定しているときは安定しているが、ダメなときはダメと出来栄えが少しボラタイルになる。調整対象が Alt と HDG の2軸となり複雑になるため、調整を体得できている (と感じる) シーンでは2軸であっても安定してこなせるが、「どちらへ操作するのだっけ?」と頭で考えているシーンではメタメタになっていると思われる。以前よりはだいぶんと技量向上したものの、瞬時に正しい方向へ調整するように身体が自然と反応するようにならなければならない。

Steep Turn

f:id:Crayon:20201025222010j:plain:right:w600同じ訓練科目ばかりでは飽きるでしょうと、訓練課程から外れてというか先行して、ジェットで初めての Steep Turn (Bank 45°) をやることに。Bank 角 30° を超えると "Bank Angle! Bank Angle!" の警報が鳴りやまない。ジェットエンジンは Power が操作に遅行するし大型機は慣性があるから、きっと操作はグダグダになるかと思いきや、教官に教わった

  • Pitch は 5° (同時に Bank は 45°) を常に維持
  • Pitch を維持することで Alt はほぼ維持できる (= Alt と VSI をスキャンして Alt を維持する Pitch を保つ) 一方で A/S が落ちてくるためそれを Power 操作して補う

という2点を気を付けていれば驚くほどきれいに Steep Turn が決まる。Pitch が維持できなければもちろん、Bank 角が 2° ズレても教官から指摘が入る。脇を締めて Pitch & Bank を維持する。Bank 45° で 5° ラインに Pitch を固定すると PFD 上の正方形のポインタがそろばんのように串刺しになるので、これをダイヤモンドと呼ぶらしい。この状態 (写真参照) の維持を目指す。ちょっとした他の Tips は下記の通り。

  • Bank 30° を超えるくらいから Power を入れ始める
  • Power 調整の目安は 5% 程度
  • Roll Out は Bank 角の半分くらいから始める ... これは Steep Turn に限らず、ジェットに限らず

Steep Turn でも、Pitch で Alt 維持、Power で A/S 調整というのが通念となっている。

本日の重要ポイント

  • (VSI でフゴイドの先を読みつつ) Pitch で Alt 維持、その上で出来上がる A/S を Power で調整、が基本
  • Steep Turn では脇を締めて Pitch 5° & Bank 45° を維持、Power 調整は 5% 程度
  • Pitch + Bank は常にウォッチし、Alt + VSI *2、A/S + Power、HDG は横目で瞬時に確認
  • Yoke は正しく握る、Elevator Trim Switch は使う時だけ指を遣る

*1:おそらく「Pitch = Alt 調整 → Power = A/S 調整」がいつでも (= 所望諸元との乖離が大きいときでも) 応用が利く調整の基本形、ただし、この手法では操作してから機体の反応を伺ってリアクションすることになり Stabilize までの所要時間が長くなるため、目標水準感がわかっている (Power = 63% で Level Flight になるなど) 、かつ、乖離が小さいとき (Descent Level Off の場合に目標 A/S + 2 kt 未満など) に微調整するために使う手法が「Power 先行 → Pitch 遅行」なのだと思う。

*2:おそらく VSI などは視線を遣らず、周辺視野で確認できるようになるとプロ水準なのだと思う。

Training in Commercial Pilotage with FTD/FFS - Constant-Rate-and-Speed Climb / Descent, Lead Design for Level Off

f:id:Crayon:20200925145809j:plain:right:w320羽田空港内某所で Flight Simulator による F/O 訓練。B737-Max で 2.0H。現役コパイロットである Y 教官からレクチャーを受ける。定期運航路線が減って現役パイロットも副業しなくてはならないのだろうか ...

羽田空港は沖留めこそ少なくなったが、ボーディングブリッジにつながれている機は多い。国際線ターミナルに Delta Airlines の B777 っぽい機が 2 機留まっているが乗客は少なく、旅客型に貨物搭載する Combi 運用となっているらしい。

Constant-Rate-and-Speed Climb / Descend

初対面である Y 教官と RJTT 34R から Take Off Climb し、いつものように 240 kt & 6,000 ft を目指す。お互いに慣れていないこともあって Climb Setting や機体重量がいつもと少し違うのか *1、A/S が伸びないなぁなんてぼーっと考えているうちに 5,000 ft 時点で A/S 203 kt, Pitch 16°, VSI 4,000 ft/min, Power 93% とメチャクチャになってしまう。 Pitch Too High & Power Too Much のため、当然 Overshoot する。これで Level Off Lead が苦手だと見抜かれたのか、本日のテーマは Climb / Descend での Level Off Lead になる。

前回の訓練で Autopilot が作る諸元を憶えたため、Climb / Descend (VSI 500 - 1,000 ft/min) での A/S, Pitch, VSI はスムーズに作れるようになった。しかし以前得たノウハウである VSI x 1/10 (ft) から開始している Level Off はうまいとはいえない。

そこで、今回、教官から教えてもらったコツは「ジェットでの Level Off は VSI x 1/10 (ft) で Power を先に調整し、VSI x 1/20 (ft) で Pitch を後に調整する」のが基本ということ。同時に実施すると操作がバタバタとなり安定しないことと、Power (の操作に遅行して出力) が調整されてくると Pitch 諸元も変わってくることが理由。さらに応用として、A/S が低ければ Power Up して速度を付けなければならないため Power 操作をより早めにする、逆に高ければ操作を遅めにというタイミング調整をする。

これが正鵠で、実践してみると驚くほどドンピシャに Level Off できる。VSI 500 - 1,000 ft/min での Level Off は完璧といわれるほど。Pitch はやや激しく調整することもできるため Power を操作した後でも十分に間に合い、Power → Pitch という調整は心に余裕をもたらす。この方法だと、これまで Climb Level Off よりも難しいように思えた Descent Level Off の方がより簡単にできる。

ということで、今度は実践の応用編。Idle Descent と N1 Climb をやってみる。A/S を 240 kt 一定にしつつ Power を Idle / N1 (Green Marker = 88% くらい) にして Descend / Climb する。Idle Descent では VSI 2,000 ft/min くらい出るため 200 ft 手前で Power 調整、100 ft 手前で Pitch 調整でピッタリ Level Off できる。N1 Climb では 2,500 - 3,000 ft/min くらい出る上に、一般に Δ VSI / Δ Power は上昇よりも下降の方が小さいため、Level Off がより難しい。 200 ft 手前ではまったく間に合わない。おそらく 300 ft 手前くらいから Power を一気に絞らないといけないのではないか *2。Alt の Trend Vector が調整開始のもう一つの目安になる、が、これを追っていては N1 Climb の Level Off はおそらくうまくできない。

よくよく考えると、VSI 500 - 1,000 ft/min の時の ΔPower は -28% (Descend) / +10% (Climb) であるが、Idle Descent / N1 Climb の時は -30% (Descend) / +25% (Climb) と通常の Descend / Climb に比べて ΔPower の増分が Descend で大きく変わらないのに対し Climb で 2.5 倍となっている。ΔPower を戻す分の操作量が Climb 側で相対的に大きくなっているのだが Throttle が重く戻すのに時間がかかることも勘案すると、当然 Climb では Lead を相当にとらなければならない。そしてまた一方で、これこそが Constant Rate-and-Speed Descent で Constant Speed とならずに増速してしまう理由でもある。Power Idle まで下げてもなお Power 過多なのだ。

というわけで Roll Off Lead は下表のような感じになるのではないかと推測する。(カッコ内数値は目標ではなく結果として到達するであろう水準。)

A/S Pitch VSI Power ΔPower Level Off Op. Start
Power Pitch
Climb N1 240 15.0-17.5 *3 (+5,000) 88% +25% -300 ft -150 ft
1,000 ft/min 240 5.00+ +1,000  73% +10% -100 ft -50 ft
Straight and Level 240 3.75  0  63% 0% *** ***
Descend 1,000 ft/min 240 2.50  -1,000  35% -28% +100 ft +50 ft
Idle 240 1.25  (-2,000) 33% -30% +200 ft +100 ft

本日の重要ポイント

  • Level Off は Power → Pitch という順に操作する
  • Level Off Lead は Power = VSI x 1/10 ft 手前, Pitch = VSI x 1/20 ft 手前で調整開始が基本
  • Power の調整開始点は A/S によって調整する (増速 / 減速する必要があるなら早め / 遅めに)
  • Idle Descent なら Power は 200 ft 手前で調整開始するくらいがよい
  • N1 Climb なら Power は 300 ft 手前で調整開始するくらいがよい
  • 調整開始のもう1つの目安は Alt (や A/S) の Trend Vector
  • VSI 1,000 ft/min との ΔPower の差分 (Throttle 調整量の違い) は、Idle Descent ではほとんどないが、N1 Climb では 2.5 倍

*1:確かに違うのだが、これは言い訳でしかない。

*2:ということが気になり始めると、Power を操作する前に EICAS を確認し、操作した後にまた EICAS を確認する、という以前の指摘は自然にできるようになる。

*3:当該 Power Setting で A/S 240 kt を維持する Pitch。

Training in Commercial Pilotage with FTD/FFS - Constant-Rate-and-Speed Climbing / Descending Turn

羽田空港内某所で Flight Simulator による F/O 訓練。B737-Max で 2.0H。

Constant-Rate-and-Speed Climbing / Descending Turn

まずは K 教官といつものとおり Take Off from RJTT RWY 34R から 6,000 ft, 240 kts での Straight & Level Flight。ちょっと集中力を欠いて Level Off で Overshoot する。

その後、本日の訓練目標である Constant-Rate-and-Speed Climb / Descend。それなりに滑らかに Climb / Descend できる。前回指摘された Scanning のコツを活かし Heading Deviation はほとんどなく、VSI Rate も安定して操作できるものの、A/S Deviation は ± 5 kt ほどズレてしまう。おそらく Throttle 調整のタイミングと量の判断が遅いため (特に本日は頭の回転が鈍いため) だと思われる。

本日は Constant-Rate-and-Speed Climb / Descend の訓練に終始すると思っていたら、今度は Turn を加えて、 の訓練に入る。当然だが Turn が入ると Smooth Transition はさらに難しくなる。Roll Out Lead は Turn と Climb / Descend が重なっていようがいまいが、それぞれまあうまくこなすが、Bank を傾けた時に、1) 適正な Bank を維持する -- 当て舵が重要、2) 適正な Alt と VSI を維持する -- Pitch が重要、3) 適正な A/S を維持する -- Throttle が重要、4) Altitude Level Off Lead を考える、5) Heading Roll Out Lead を考える、を同時に実行するのはやはり難しい。2), 3) はもとから難しいが、Turn が加わると 1), 5) に思考を取られる分、さらに難しくなる。

MCP Operation

f:id:Crayon:20200831153513j:plain:right:w480本日は夏バテと睡眠不足のため、2 時間全部を集中力が必要なマニューバ訓練に充てるのは難しいことは初めから自覚していた。近々、Microsoft Flight Simulator 2020 を購入することから Autopilot Mode Control Panel (MCP) の操作方法を熟知したいということもあり *1、後半戦はなんとかマニューバ訓練を切り上げ、MCP 操作を教えてもらおうと予め画策していた。そうしたら図らずも、教官も Constant-Rate-and-Speed Climbing / Descending Turn 訓練の一環で諸元を理解したほうがよいだろうと思い立ち、MCP を弄って Autopilot の諸元を参考にしようということになる。

Autopilot の諸元は驚くほど滑らかで (特に Throttle の) 操作量がきわめて少ない、が、手動で操作したらこの操作量ではすまないであろう。そこは少し割り引いて (というか割り増して) 考える必要がある。ビデオを見返し、後日まとめた諸元は下表のとおり。


A/S Pitch Bank VSI Throttle Note
水準 増減 水準 増減
Climb 240 5.00+ +2.00 +1,000 73% +10%
Straight and Level 240 3.75 0.00 0 63% 0%
Descend 240 2.50 -1.25 -1,000 35% -28%
Climbing Turn No Data
Turn 240 5.00- +0.60 30° 0 65% +2%
Descending Turn 247 2.50- -2.00 30° -1,000 33% -30% +7 knot *2

本日の重要ポイント

  • Constant-Rate-and-Speed Climbing / Descending Turn は以下を同時に考える
    1. 適正な Bank を維持する -- 当て舵
    2. 適正な Alt, VSI を維持する -- Pitch (Pitch は適正水準の数値を憶える)
    3. 適正な A/S を維持する -- Throttle (Throttle は適正水準の数値を憶える)
    4. 適正な Altitude Level Off Lead をとる (Lead は適正水準の数値を憶える)
    5. 適正な Heading Roll Out Lead をとる (Lead は適正水準の数値を憶える)

*1:12 年前 Microsoft Flight Simulator X の MCP と FMS (と飛行諸元) をいま一つ理解できなかったことが実機で訓練してみようと思ったきっかけであり、結果としてパイロットライセンスを取得したことにつながっている。ディープな世界を独学するのは難しい。

*2:Turn よりも Pitch と Throttle を下げているため、増速している。

Training in Commercial Pilotage with FTD/FFS - Acceleration/Deceleration, Constant-Rate-and-Speed Climb/Descent

羽田空港内某所で Flight Simulator による F/O 訓練。B737-Max で 2.0H。f:id:Crayon:20200711105104:plain:right:w480訓練前にターミナルビル1階の航空神社にお参りしたご利益か、今日は実り多く、また、最初から操縦が非常に滑らかかつ安定的で終始「練度が高い」感を醸し出す、充実の一日となる。

Take Off Climb Step Check

Take Off Climb を少し練習した後にすぐに N 教官の Step Check へ進むも、練習初回が最も上手に行く。B737-Max の諸元をまだよくつかんでいないからこそ、「諸元の水準を合わせればなんとかなる」的な雑なリード設計から脱して、少し動かしてはその反応を見て操作必要量を探りつつ所望諸元に近づけるようこまめに当てに行く Feedback & Reaction が上手にできるようになっている。

Acceleration / Deceleration

次の訓練科目 Acceleration / Deceleration へ進む。常に Alt 6,000 ft を維持しつつ増減速する。最初に A/S 240 kt から 200 kt へ減速し安定させたら、 240 kt へ増速し安定させる。次に Configuration Change (Gear Down & Flap Down) を伴う140 kt への減速、つまりは Slow Flight へ移行する。低速域でただでさえ難しい制御が Configuration Change で Drug が増えさらに難しくなる。しかしそこはセスナの実機操縦で得意だった Slow Flight、逸脱を小さく安定させる。再度 240 kt へ増速したら、 N 教官から「最後はお遊びで 120 kt まで落としてみますか」との指示で Super Slow Flight へ移行。A/S をちょっと下方逸脱すればすぐ Stall する、加えて機位は群馬・新潟県境の山岳地帯、Alt をちょっと下方逸脱すればすぐ CFIT になる、というすべてが超スレスレの Risky Flight。A/S が 118kt くらいになると A/S Gauge が黄色くなり Stall Warning *1 が鳴るが、逸脱を最大でも A/S -5 kt, Alt -500 ft, Heading 25° くらいに収め、急な Attitude Change をしないよう、Bank を傾けすぎないよう操作して安定させる。低速域だと TO/GA くらいに Power Set しないと Alt の損失を埋めるのに至らない。

A/S 140 kt w/ Configuration Change は難しいが「よく考えるとこれは Final Approach ですね」と教官に振ると、Pitch を下げて Descent している Final Approach と比べて、Straight & Level @ 6,000 ft は Pitch Up しないとできないため、Drug がより強く発生しており、それだけより難しいとのこと。この過程で気づいたことがいくつか。

  • Alt & A/S を Power だけで調整はできない
    • 当たり前だが Pitch でも Alt & A/S は変わる。Pitch と Power で調整する。
  • 探りながら細かく調整する、でよい
    • Next Action は調整の結果を見極めてからでよい。設定を変えたら、高度安定するまで待つ、速度安定するまで待つ、エンジン音周波数が安定するまで待つ。
  • Power Setting の調整は、Climb では易しく、Descent では難しい
    • Power は Idle (Min) まで下げても高度がなかなか下がらない一方、(極端な低速域でもない限り) Power を To/GA (Max) まで上げなくても高度は上がるため、所望高度での Level Off における Power 変量は Climb で 少なく、Descent で多くなる。(2,000 ft 移動して 6,000 ft 目標の場合、それぞれ 73% → 58%、23% (Idle) → 58% くらい。)
    • Power 変量が多ければ Attitude Change が大きくなる。妥当な Attitude から逸脱すれば Drug が増え、Feedback が狂って Overshoot し、調整が収束しにくくなる。必然的に、調整は一気にはできず、長くこまめに行うことになる。
    • Alt & A/S は少し超過気味に調整しておいて、Pitch 操作で Drug を作って微調整するという方法もありそう。特に、低速域・低高度でのAlt や A/S は、超過は許容、不足は非許容とするのがよい。
  • Pitch の微調整を Trim でやってはいけない
    • これはよく言われるあるあるだが、今回初めて実感として理解できた。安定的に Straight & Level している最中ならばまだ許されるかもしれないが、Power Setting Change, Configuration Change, Gust 等々の Pitch を大きく動かすファクターがある中で Trim で Pitch 調整をやってしまうと、どこが Pitch の妥当な水準か簡単に見失う。実際、今回、Trim でちょっとした Pitch Up を図ったところ、おそらくその時に教官が Flap Down したのであろう、簡単に Pitch が 5° ほど Alt が 1,000 ft ほど跳ね上がってしまった。

Constant-Rate-and-Speed Climb / Descent

最後の訓練科目 Constant-Rate-and-Speed Climb / Descent を実施する。まず 1,000 ft/min で 10,000 ft まで Climb してみる。Constant-Rate-and-Speed であっても、フゴイドを勘案した上である VSI を基準点 に Pitch を作ることは Straight and Level と同じ、VSI x 1/10 (ft) 程度の Lead を取って Level Off する、と以前に学んだことを活かし、そのとおりに実演してみる。次に 500 ft/min で 8,000 ft まで Descend する。こちらでも、Descend では Level Off Lead を少し大きめにとる、と以前の知恵を活かす。

途中、教官に1回 Pause され「Heading の修正が遅い」と指摘される。反省すべき点は 2つある。

  • Scanning が遅い、そして、Bank を調整すべき方向を確認していない
    • Pitch & Power → A/S & Alt の修正が大変であるためこちらを優先し、Bank → Heading は劣後させている自覚がある。
    • Heading の状況に関わらず、とりあえず Bank Neutral にして (おけば Heading がそれ以上ズレないため) 余力が生まれたときに転進しようと考えている。
    • Bank 調整をする前に、まず ND で修正すべき方向を確認する *2
  • Pitch を調整すると Bank も揺らぐ、が、それを修正していない
    • Pitch も Bank も Yoke 操作であるため、片方のみを意識して調整したら、もう片方がズレるのは当たり前。常に両者一体として調整する。
    • 教官にヒントをもらって体得したが「Pitch を直したら Bank も見る、Bank を直したら Pitch も見る」とすると驚くほどブレなくなるため、これをクセにするとよい。

f:id:Crayon:20200712113233j:plain:right:w480問題が複雑であることから Yoke (Pitch) と Throttle 操作で A/S + Alt (+ Power) という2次元の調整と Yoke (Bank) 操作で Heading という1次元の調整をあえて分離していたのだが、すべては右図のとおり連動しているため、また PD 上の Center Point があるべき位置は上下 (Pitch) 軸+左右 (Bank) 軸上の1点にあるはずであるから、 Pitch と Bank はセットとして修正し3次元で調整しなければならない*3ということだ。

最後に 500 ft/min で Climb してから、さらに 750 ft/min での Climb を指示され、750 ft/min Climb の Pitch を探っていると ... 教官にまた Pause され「なぜ、そんなに手探りで探る?」と問われる。つまりは、1,000 ft/min Climb と 500 ft/min Climb を経験しているのだから、750 ft/min Climb の Pitch は、全く手探りではなく、当たりがつくはずだということを問われているのだ。むー、難しい ... 私の Short-Term Memory は大きくないのよ ... さきほどの Pitch がどのくらいだったかなど憶えているわけがない。しかし、そういう観点で設計ができないのは、先の信号が赤ならばエンジンブレーキを効かせつつ惰性で進むか緩やかにブレーキをかけるだけでよいのにアクセルを吹かしてしまうタクシーのようなものだ。乗客に G を感じさせず、エコ操縦し、ついでに訓練時間も短くすます、技量の高い操縦士になるには、先々を見据えたリード設計ができるようにならなければならない。自他ともに認めるほど技量が一段階向上したことがあり、次の目標水準ができたということだ。

本日の重要ポイント

  • A/S & Alt は探りながら細かく Pitch & Power で調整する
  • 設定を変えたら、高度安定するまで待つ、速度安定するまで待つ、エンジン音周波数が安定するまで待つ
  • Power Setting の調整は、Climb では易しく、Descent では難しいため、Descent では特に早期・入念にリード設計する
  • Power 変量が多い調整は、一気にやろうとせず、長くこまめに行う、また、A/S & Alt を超過気味にしてから Pitch 操作で Drug を作って微調整することを視野に入れる
  • Pitch の微調整を Trim で行わない
  • Pitch を直したら Bank も見る、Bank を直したら Pitch も見る
  • Bank 調整をする前に、まず ND で修正すべき方向を確認する
  • 経験した飛行諸元を憶え、それを活かして先々を見据えたリード設計をする

*1:Stick Shaker ではない。

*2:教官に指摘されたが「Bank が傾いていて Heading が所望する方向へ流れているのに、わざわざ Bank Neutral にしてしまうこともある」とのこと。

*3:飛行諸元の結果として合わせるのは3次元 (A/S, Alt, Heading) だが、飛行諸元の微小変量・感応度 (Trend Vector, VSI, Pitch, Bank) と出力 (Power) も合わせないと結果の3次元を維持できないから、結局は8次元を所望諸元に合わせることになる (Trend Vector と Power は常に見ているわけではないため、実質 6.5 次元くらいになる)。

Training in Commercial Pilotage with FTD/FFS - Lead Design for Take Off Climb w/ B737-Max

羽田空港へ移転した某所で Flight Simulator による F/O 訓練。B737-Max で 2.0H。f:id:Crayon:20200614100048j:plain:right:w480

Take Off Climb w/ B737-Max

3 月に実施予定だったものの移転とコロナ騒動で何度もリスケ・延期してようやくめぐってきた、モーション付き B737-Max 新シミュレータによる初訓練。といってもモーションは自分では動かさず、遊びに来た他のお客の体験操縦を見ていただけだが。モーションなしでは FTD (Flight Training Device) だが、ありでは FFS (Full Flight SImulator) となる。

前半は N 教官と、後半は K 教官といつものとおり Take Off from RJTT RWY 34R から 6,000 ft, 240 kts での Straight & Level Flight。B737-Max シミュレータは B737-800 シミュレータと全然違う! シミュレータ自体の感触の違いとしては以下の通り。

  • York
    • York は重くなったが、実機に近くなったらしい。
    • 遊びがなくなって反応がよくなった。以前のものはニュートラル近辺では小さく動かしても感応せずグイっと大きく動かさなければならなかったが、初動にも敏感に反応するようになった。ただし重い。
  • Throttle
    • Throttle も重くなり、これは実機よりも重いらしい。
    • 静止摩擦係数が小さくなって微調整がやりやすくなった。以前のものはグイっと力を込めないと動き出さなかったが、滑らかに動き出すようになった。ただし重い。
  • PFD, ND, EICAS のディスプレイ
    • 高解像度になり見やすくなった。
    • PFD が最大 8 秒くらい固まることがある。(景色は操縦に遅延なく追随するため、グラフィックボード ~ PFD 出力系統のどこかがボトルネック。)
  • その他
    • Bank がかなり敏感になり、Wing Level で安定せず常に Roll 調整が必要になった。実機よりも敏感らしい。

B737-800 と B737-Max との間の機種の違いとしては以下の通り。

  • EICAS は PFD に近づき、視点移動を要さなくなって見やすくなった。
  • Throttle 出力がかなり強くなった。Power 諸元は B737-800 と異なるため、見直しが必要になった。

Max はかなり高出力であるため、諸元を全面的に見直す必要がある。今回やってみて機種移行訓練というものがなぜ必要か実感としてわかった。今回のテーマは B737-800 から B737-Max への機種移行訓練だ。

まず、Straight & Level はいままでの Pitch = 2.5° +, Power = 63% では通用せず、Pitch = 3.8° (2.5° と 5° の中間か気持ちやや上), Power = 57% くらいにしないと維持できない。旧機種で Check を通った身としてこのくらいはやってすぐに感覚で把握する。フゴイド運動することを見越して、VSI の揺らぎを先読みし、Pitch を安定させる。

しかし、Throttle のリード量調整を把握できていないため Altitude Transition はなかなか滑らかにいかない。Alt を 100 ft オーバーシュートするのはまだいい方で、 A/S を 25 kts もオーバーシュートすることまである。何度か Take Off & Climb to 6,000 ft をやってみて気づいたのは、高出力であるため、Take Off Climb では Pitch をいままでより高め (+1目盛り 2.5°) に設定して A/S を維持する (制限速度 250 kts を超過しない!) こと、1,000 ft to Level Off あたりに近づいたら早めに Power を絞ること。また、Descent では Power Idle くらいにまで下げないと降下しない、ということ。

そんなこんなで最後には Take Off Climb → Straight & Level Flight 移行のタイムチェックも 2 分程度に収まりそうなくらいの水準になったため、Take Off & Climb の諸元はこのくらいになるのではないかという想定値で更新 (黄色マーク) して *1、次回 Stage Check に備える。


B737-MAX A/S Pitch Alt VSI Throttle
Rotate VR 0 0 TO/GA
Rotate + 5 sec 12.5
400 ft 159 20.0 400 4,000-
3,000 ft 240 12.5 3,000 3,000-
1,000 to Level Off 10.0 5,000 2,000- 75%
500 to Level Off 5.0+ 5,500 1,000- 65%
Level Off 3.8 5,950 500- 57%

本日の重要ポイント

  • Max は高出力のため、Pitch は高い方向へ、Power は低い方向へ諸元を見直し
  • Descent は Power Idle まで下げてもよい
  • フゴイド運動することを見越して、VSI の揺らぎを先読みし、Pitch を安定させる

*1:実感としてわかったのは、エンジン性能により、安定する Pitch も変わるということ。DC-10 がメーカー推奨よりも Pitch を大きく取った方がエネルギー効率がよいと航空会社が気づいて 4° Pitch で巡航するようになり、他機種よりも通路が傾いているため「DC-10 乗務は (なぜか) 疲れる」と CA がボヤくエピソードを思い出した。

Functional Programming w/ C# LINQ - with 初期化子の代替メソッド定義 for C# 8.0 or former

C#LINQ を使ってデータ変換をしていると、データクラスのインスタンスをコピーして一部のプロパティを別の値に差し替えたいということがよくある。C# 9.0 では Records v2 の with 構文というものが導入されるようで期待できるのだが、現行の C# 8.0 では存在しないため、Clone() メソッドを作成し、リフレクションでプロパティを置換していた。

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

public abstract class Record : ICloneable {
  /// <summary>
  /// オブジェクトのクローンを Shallow Copy で作成する。
  /// (ICloneable 互換)
  /// </summary>
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  public object Clone() => Clone(null);

  /// <summary>
  /// オブジェクトのクローンを Shallow Copy で作成する。
  /// 引数で指定される匿名クラスで同名プロパティの値を置き換える。
  /// </summary>
  public object Clone(object? param = null) {
    var clone = this.MemberwiseClone();
    var subst = param ?? new {};
    var props = subst.GetType().GetProperties();
    var dic   = clone.GetType().GetProperties()?.ToDictionary( p => p.Name, p => p);

    foreach (var p in props)
      if (dic?.ContainsKey(p.Name) ?? false)
        dic[p.Name].SetValue(clone, p.GetValue(subst));
      else
        throw new ArgumentException($"{this.GetType().Name} のクローン時にプロパティ {p.Name} の置換に失敗しました。\n" +
                                    $"当該プロパティは {this.GetType().Name} に存在しません。");

    return clone;
  }
}


下記のように使う。この例ではプロパティ数が少なくてありがたみがないが、プロパティがたくさんあるデータクラスをコピーしてごく一部のプロパティしか置換しない場合に (さらに Select, Join, GroupJoin 等で何度も何度も変換する場合には特に) 重宝する。

public class RecordSample : Record {
  public string ID    { get; set; }
  public int?   Value { get; set; }

  // list_original を基にしてプロパティ Value の値を2乗にしたもうひとつのシーケンス list_converted を作成する例
  public static void Test() {
    var list_original =
      Enumerable.Range(0, 10)
        .Select( x => new RecordSample {
          ID    = "" + x,
          Value = x     ,
        } )
          .ToList();

    var list_converted =
      list_original
        .Select( org => org.Clone(new { Value = org.Value * org.Value }) as RecordSample ) // ← ココで使用
          .ToList();
  }
}


これまで、毎度毎度リフレクションでプロパティ走査をするのは非効率であることは自覚しつつも、このメソッドに特に不満はなかった。が、しかし、最近 PostgreSQL サーバをたてて大量データを扱うようになり、このオーバヘッドが気になったため、この記事を参考に式木を使ってリフレクションを極小化してみた。

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

using static System.Linq.Expressions.Expression;

public abstract class Record : ICloneable {
  private static readonly IDictionary<string, Action<object, object>>DicAction = new Dictionary<string, Action<object, object>>();

  /// <summary>
  /// プロパティの型をチェックする。(静的メソッド)
  /// </summary>
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private static bool CheckPropertyType(Type dst, Type src) =>
    dst == src ||                                              // T  dst <=> T src ... acceptable
      (dst.IsGenericType                                    && // T? dst <=> T src ... acceptable
       dst.GetGenericTypeDefinition() == typeof(Nullable<>) &&
       dst.GenericTypeArguments.FirstOrDefault() == src      );

  /// <summary>
  /// プロパティの値を置換する Action を作成する。(静的メソッド)
  /// (リフレクションを極小化するための式木)
  /// </summary>
  private static Action<object, object> CreatePropertyReplaceMethod(Type type_dst, Type type_src) {
    var props = type_src.GetProperties();

    // 式木構築前の型チェック (不都合がある場合は Runtime Exception)
    props
      .Select( p_src => type_dst.GetProperty(p_src.Name) switch {
        // 置換先のプロパティ存在チェック
        null      => throw new ArgumentException($"プロパティ {p_src.Name} は置換先インスタンスのクラス " +
                                                 $"{type_dst.Name} に存在しません。"),
        // 置換先のプロパティ型チェック
        var p_dst => (p_dst.PropertyType, p_src.PropertyType) switch {
          (var t_dst, var t_src) =>
            CheckPropertyType(t_dst, t_src) ?
            true : throw new ArgumentException($"プロパティ {p_src.Name} の型 {t_src.Name} が置換先インスタンスのクラス " +
                                               $"{type_dst.Name} のプロパティの型 {t_dst.Name} と一致しません。"),
        },
      } )
        .ToList();

    // Lambda Parameters
    var o_dst = Parameter(typeof(object));              // object dst (1st param of Action)
    var o_src = Parameter(typeof(object));              // object src (2nd param of Action)
    var t_dst = Parameter(type_dst);
    var t_src = Parameter(type_src);

    // Lambda Body
    var body  =
      (Enumerable.Empty<System.Linq.Expressions.Expression>()
       .Append(Assign(t_dst, Convert(o_dst, type_dst))) // T1 t_dst = (T1)o_dst;
       .Append(Assign(t_src, Convert(o_src, type_src))) // T2 t_src = (T2)o_src;
       .Concat(props
               .Select( x =>
                        Assign(Property(t_dst, x.Name), // t_dst.Prop[x] = (t_dst.Prop[x] の型)t_src.Prop[x];
                               Convert(Property(t_src, x.Name),
                                       type_dst.GetProperty(x.Name)?.PropertyType)) ))); // 不存在チェック済み

    // Compiling Lambda
    return
      (Action<object, object>)
        Lambda<Action<object, object>>(Block(new [] { t_dst, t_src }, body), o_dst, o_src)
          .Compile();
  }

  /// <summary>
  /// オブジェクトのクローンを Shallow Copy で作成する。
  /// (ICloneable 互換)
  /// </summary>
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  public object Clone() => Clone(null);

  /// <summary>
  /// オブジェクトのクローンを Shallow Copy で作成する。
  /// 引数で指定される匿名クラスで同名プロパティの値を置き換える。
  /// </summary>
  public object Clone(object? param = null) =>
    new Func<object, object, object>((dst, src) => {
      var type_dst = dst.GetType();
      var type_src = src.GetType();
      var pattern  = $"{type_dst.Name}/{type_src.Name}";

      if (! DicAction.TryGetValue(pattern, out var action))
        DicAction.Add(pattern, action = CreatePropertyReplaceMethod(type_dst, type_src));

      action(dst, src);

      return dst;
    })(this.MemberwiseClone(),  // Clone shallow-copied
       param ?? new {}       ); // Substitute
}


クラスの辞書を静的に保持してキャッシュとし、同じクラスの組み合わせが来たらプロパティを置換するアクションをキャッシュから抽出して利用する。また、置換先 Record のプロパティの型が T? のときに置換元匿名クラスのプロパティをいちいち Nullable にキャストして指定するのが面倒であるため、Nullable か否かの違いは許容するようにしている。

  // もし、置換元と置換先のプロパティの型を完全一致させなければならない場合は、こう書かなければならない
  .Select( org => org.Clone(new { Value = (int?)1 }) as RecordSample )

  // 置換先のプロパティが int? であろうがこう書きたいため、T? <=> T の違いは許容するようにしている 
  .Select( org => org.Clone(new { Value = 1 }) as RecordSample )

  // 置換元のプロパティが null である場合は、キャストせざるを得ない (匿名型は型不定のプロパティを許容しない) 
  .Select( org => org.Clone(new { Value = (int?)null }) as RecordSample )


Record のプロパティ数 4、匿名クラスによる置換プロパティ数 1 の小さなデータクラスで試したところ、4 - 5 倍の高速化が図れた。めでたしめでたし。

繰り返し回数 100,000 1,000,000
旧版 (Reflection Only) 3,565ms 27,582ms
新版 (Expression) 692ms 6,695ms
高速化 5.2 倍 4.1 倍


実運用目的の式木を初めて自作してみて勉強になったが、コンパイル時、ランタイム初回実行時 (キャッシュがないとき)、ランタイム2回目以降実行時 (キャッシュヒットのとき) と 3 層構造で考えなければならず、この程度のロジック構築に半日もかかってしまった。

LINQ to Entities でロジック構築する場合も、LINQ to Object 互換レベル (IEnumerable)、LINQ to Entities 互換レベル (IQueryable)、データベースプロバイダー互換レベル (PostgreSQL では OK だが Oracle では NG というようなケース)、データベース実装互換レベル (Oracle 12c では OK だが Oracle 11g では NG な Apply 文など) と 4 層構造で考える必要がある。メタプログラミングは (おもしろいが) 非常に疲れる。