2024,江端さんの忘備録

論文作成に疲れて、お茶でも飲もうかリビングに降りていったら、嫁さんが、アマプラで、実写版の「3月のライオン」を見ていました。

I was tired of writing my paper, so I went to the living room for tea and found my wife watching a live-action version of "March Lion" on Amazon.com.

将棋といえば、今や空前の将棋ブームと言われています。

Shogi is now considered to be amid an unprecedented Shogi boom.

私は、将棋のルールを、おぼろげに覚えているくらいですが、将棋を扱ったマンガコミックは、かなり読んでいると思います。

I can only vaguely remember the rules of Shogi, but I think I have read a lot of manga comics dealing with Shogi.

「二人零和有限確定完全情報ゲーム」

で、それらのマンガを読んでいて思うことは、

And when I read those comics, I come to believe,

―― 本気で将棋をやる人(プロ)は、誰もが、苦しみにのたうち回りながら将棋をやっているのだろうか

"Does any professional Shogi player play the game while suffering from the continuous harsh reality?"

と思ってしまうことです。

もちろん、マンガにはドラマ性が必要だとは思うのですが、それにしても、将棋のマンガに登場する人物 ―― 全員、幸せそうに見えない。

Of course, I think comics need drama, but even so, the characters in chess comics -- don't all look happy.

彼らの悶え苦しみながら「勝ち」に執着する姿勢は、凡庸たる私を魅了します。

Their agonizing and painful obsession with winning fascinates me.

では、私も、悶え苦しみながら「勝ち」に執着する人生を送りたいかといえば、『絶対イヤ』と応えることができます。

But if I were to ask myself whether I, too, would like to live a life of unbearable and painful obsession with winning, I would answer, "Absolutely not.

-----

日本将棋連名の「奨励会」と言われる組織の「退会規約」を読んでいたのですが、『年齢制限の強制退会』の内容が怖い。

I was reading the "withdrawal agreement" of the organization called "Shourei-kai" (Japan Shogi Federation), and I felt fear about the contents of the 'forced withdrawal of age limit.'

ただ、この年齢制限というのが、強い組織を維持する目的としては正しいことは分かります。

However, I understand this age limit is correct for maintaining a solid organization.

年齢制限がないと、多分、長老とか派閥とか、そういうものが必ず発生し、組織を形骸化、弱体化し、さらには不法行為にも及ぶからです。どっかの国の与党のように。

If there is no age limit, there will probably always be elders, factions, and such, shaping and weakening the organization and even leading to illegal activities, like the ruling party in some countries.

-----

年齢制限と言えば、数学のノーベル賞といわれている、「フィールズ賞」も、「4年に一度」「40歳以下」「2名以上4名以下」という厳しい制限があります。

Speaking of age restrictions, the "Fields Medal," considered the Nobel Prize in mathematics, also has strict limits: once every four years, under 40 years old, and between two and four recipients.

数学の場合、偉大な証明は若い数学者によって成されることがほとんどであることを鑑みれば、別段、年齢制限なくても良さそうですが。

In the case of mathematics, I think that no age limit is needed, given that most of the great proofs in mathematics are done by young mathematicians.

(「フェルマーの最終定理」の証明に成功したアンドリュー・ワイルズさんは、1998年に45歳で「特別賞」を受賞していますが、これは例外です)。

(Andrew Wiles, who successfully proved "Fermat's Last Theorem," received a "special award" in 1998 at the age of 45, but this is an exception.)

-----

ともあれ、『血みどろの人生』は、自分ではなく、他人にやってもらうのが、一番いい。

It is best to have someone else do the "bloody life," not me.

私は、その人の人生を、『見せてもらって』『楽しませてもらう』で、十分です。

It is enough for me to be 'shown' and 'entertained' by the bloody life of another.

2024,江端さんの忘備録

Adoさんのヒット曲「うっせぇわ」は、基本的には上司に対する「口にできない部下」の不満、つまりジュニアからシニアへの批判の歌だと思っています。

I believe that Ado's hit song "Ussei-wa" is about the frustration of "unspoken subordinates" against their bosses, i.e., a song of avoidance and criticism from junior to senior.

でも、逆のパターンもあるのです。

But there is a reverse pattern.

シニアからジュニアに対する「うっせいわ」です。

It is a "Ussei-wa" from seniors to juniors.

-----

シニアは、生きている時間が長いので、本人の資質に関係なく、どうしたって経験値は高くなります。

Seniors have been alive longer, so they have more experience no matter what, regardless of their qualities.

ジュニアより、直面したトラブルは多く、そして、トラブルそれを解決しなければ仕事進めることができませんでした。

I faced more troubles than Junior and had to solve them before proceeding with my work.

もちろん、この経験値の高さは、別に本人の努力とか才能とか関係なく、単なる時間の長さというだけの話です。

Of course, this high level of experience has nothing to do with their effort or talent but the length of time.

だから、シニアは、その経験値を「活用する」とは当然としても、これをジュニアに「自慢する」のは理不尽ですよね。

So, while it is natural for seniors to "take advantage" of their experience, it is unreasonable for them to "brag" about this to juniors.

そういうことをするシニアは、単なる「バカ」ですが、このようなバカは一定数います。

Seniors who do such things are simply "idiots," but there are many such idiots.

-----

それ故、ジュニアから見れば、シニアは『自分の仕事を邪魔するだけに存在している』かのように見えるかもしれません。

Hence, from the junior's point of view, it may seem as if the senior 'exists only to get in the way of his work.

上記のようなバカなシニアは一定数いますが、大多数は、そうではありません。

While a certain number of seniors are idiots, as described above, the majority are not.

『私がはまった地雷に、何も、お前もはまることもあるまい』という善意だったりするのですが ――

They have good intentions by saying, "You don't have to get stuck in the same landmines I got stuck in.

ただ、仮に善意であったとしても、ジュニアにとって「うっせいわ」という気持ちになるのは、自然です。

However, even if the intentions were good, it is natural for Junior to feel "Ussei-wa."

-----

私がジュニアだったころ、『私には、失敗する権利もないのか?』と言いたくなったものです。

As a junior, I was tempted to say, 'Don't I have the right to fail?"

―― 例えば、私が20歳代にやってきた時『バカ、アホ』と言われたことを、今やると『流石は、江端さんですね。素晴しい』って言われるぞ

それ故、私はだれかの『失敗する権利』を邪魔したくありませんので、失敗する可能性のあるジュニアを目の前にしても、ニッコリ笑って佇んでいることができます。

Therefore, I do not want to interfere with anyone's "right to fail," so I can stand smiling in front of a junior who may fail.

ちょっと話が逸れました。元に戻します。

I got a little off-topic. Let me get back on track.

-----

シニアからジュニアに対する「うっせいわ」とは何か? ―― それは、『コスト試算がされていない無茶な提案』です。

What is "Ussei-wa" from seniors to juniors? -- It is a reckless proposal that has not been cost-estimated.

私くらいのシニアになれば、提案の内容を聞くだけで、そのコストや期間がどれくらいで、何人くらいのリソースを投入しなければならないかが、さくっとイメージできます。

When you are as senior as I am, you can quickly visualize the cost, time frame, and number of resources that would be required just by listening to the proposal.

加えて、その失敗する可能性も、直感で予測できます(数値化や論理化するのが面倒ですが)。

In addition, the likelihood of its failure can be intuitively predicted (although it is tedious to quantify and logically convert it).

で、まあ、新しい試みというのは、大抵の場合、失敗します ―― しかし、それは、仕方がありません。「新しい試み」なのですから。

And, well, new attempts usually fail -- but that can't be helped. It's a "new attempt.

-----

そして、シニアのこの『直感の予測』が、ジュニアには『シニアが自分の仕事を邪魔するだけに存在している』ように見えるのです。

This 'intuitive prediction' of the senior seems to the junior to be 'present only to interfere with the junior's work.

ただ、この件に関しては、ジュニアに対してロジカルに説明しない(or できない)シニアが悪い。その説明で「手を抜いている」なら、さらに悪質と言えます。

However, in this case, the senior does not (or cannot) explain logically to the junior. It is even worse if they are "cutting corners" with that explanation.

でも「ロジカルな説明は面倒くさい」です。

But "logical explanations are troublesome".

それ故、シニアは「ジュニアを放っておく」という選択肢を選ぶことがあります。

Hence, the senior may choose to "leave the junior alone."

こうして、シニアもジュニアも、共に争わず、ニッコリ笑いながら、双方、不幸になっていく ―― と。

In this way, both seniors and juniors do not fight. Both smile and laugh, and both are unhappy

まあ、私は、それはそれで「落とし所」の一つだと思っています。

Well, I believe that is one of the "solutions."

-----

ちなみに、これまで私は、才気溢れる若い才能たちが、果敢に挑戦し、そして、無様に転げ落ちていく様を、沢山見てきました ―― ニッコリと笑いながら。

Incidentally, I have seen many brilliant young talents boldly take up the challenge and then fall in a heap -- with my smiling.

このような「才気溢れる若い才能」は、生意気で、不遜で、不愉快な奴が多かったので、苦労することなく、心の底から「ニッコリ」することができました。

These "brilliant young talents" were often cocky, irreverent, and obnoxious, so I was able to "smile" from the bottom of my heart without any difficulty.

-----

このように、私の人生のモットーの1つは

Thus, one of my life mottos is

『「才気溢れる若い才能」の邪魔はしない。でも、助けもしない』

"I don't get in the way of brilliant young talent, but I don't help it either. But neither do I help them.

です。

2024,江端さんの忘備録

最近、職場と大学の両方で、ラズベリーパイ(ラズパイ)を使ったシステム構築を行っています。

I have recently built systems using the Raspberry Pi (Raspi) at work and university.

途中まで作ったシステムを、その後の自分の作業で壊してしまう ―― 私は、こんな場面に山ほど立ちあってきました。

I have seen many such situations where my subsequent work destroyed a system halfway through construction.

ラズパイは、途中まで作ったシステムをSDカードにクローンとして残しておけるので、私は、そのSDカードのイメージファイルをPCに格納しておくことができます。そして、必要に応じて、SDカードを複製できます。

The Raspi can keep a clone of the system I have made halfway through on an SD card so that I can store the SD card image file on my PC. I can then duplicate the SD card as needed.

このクローンがあることで、安心して開発を続けることができます。

With this clone in place, I can continue to develop with peace of mind.

-----

ところが、これには、別の問題もあります。

However, there is another problem with this.

(1)SDカードを職場や大学に発注すると、1~2週間はかかる上に、書類作業が発生してしまい、直ぐに作業に取りかかれない

(1) When SD cards are ordered from workplaces or universities, it takes one to two weeks, and paperwork is required, so the work cannot be started immediately.

(2)ラズパイが取り扱うSDカードの容量が、日々増大している(今は128GB)ので、私が個人でいくつも保有しているSDカードが使えない

(2) The capacity of the SD card that Raspi handles is increasing daily (128GB now), so I can't use the SD cards that I have personally.

そんな訳で、ついつい、Amazonのサイトに行って、SDカードの値段を調べてしまいます。Amazonなら、大抵の場合、翌日に配送されるからです。

That's why I always go to Amazon's website to check the price of SD cards; Amazon usually delivers them the next day.

"1500円 + 翌日"と、"2週間 + 書類作成 + 何回もの稟議"を秤にかけて、Amazonの購入ボタンに手が出てしまう私を、一体、誰が責められましょうか。

Who could blame me for reaching for the Amazon purchase button when I weighed "1,500 yen + next day" against "2 weeks + paperwork + multiple requests for approval"?

-----

最近の更なる問題は、SDカードの容量です。

A further recent problem is the capacity of SD cards.

私が最初にラズパイを使ったとき、SDカードは512MBでした。

When I first used Raspi, the SD card was 512MB.

1GB, 4GB, 16GB, 32GBと大きくなり、今や128GBです(江端家ビデオ監視システムのラズパイで32GB)。

It has grown to 1GB, 4GB, 16GB, 32GB, and now 128GB (32GB for Raspi, the Ebata family's video surveillance system).

嫁さん:「私達が、この家に滞在している最中は、システムを停止して」

バックアップの時間は、十数分から4時間に伸び、そして、クローン用のイメージファイルが私のPCのHDDを圧迫しています。

Backup time has grown from a dozen minutes to four hours, and then the image files for the clone are straining my PC HDD.

-----

『これからも、このようなITガジェットに投資し続けなければならないのかなぁ』と思うと、ちょっと憂鬱(ゆううつ)な気分になります。

I feel a little depressed, 'I wonder if I must keep investing in these IT gadgets from now on.

 

2024,江端さんの技術メモ

ChatGPTと相談しながら、fastapiの起動時にコマンドを起動する方法を試していたのですが、上手く動かない(最初のコマンドでロックしてしまう)ので、起動したまま、そのまま放置するコード(プロセスを落したければ、コマンドから自力でkillコマンドで落す必要ある)にしました。

import subprocess
from fastapi import FastAPI

app = FastAPI()

# 各コマンドを実行する関数
def run_command(command):
    try:
        # バックグラウンドでプロセスを実行
        subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
    except Exception as e:
        print(f"コマンド '{command}' の実行中にエラーが発生しました: {str(e)}")

# 各コマンドを実行
commands = [
    "sudo socat udp-listen:38089,reuseaddr,fork udp:192.168.3.153:38089",
    "sudo socat udp-listen:38090,reuseaddr,fork udp:192.168.3.153:38090",
    "sudo socat udp-listen:38091,reuseaddr,fork udp:192.168.3.153:38091"
]

for command in commands:
    run_command(command)

@app.on_event("startup")
async def startup_event():
    print("Application startup complete.")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

 

2024,江端さんの忘備録

こちらも、やはり『やめろ』の声が高かった「ゴールデンカムイ」の実写化です。

This is another live-action adaptation of "Golden Kamui," for which there were still high voices of "Don't do it.

今、ナイトシアターから帰ってきた、嫁と娘を駅でピックアップしてきたのですが、

I just picked up my wife and daughter at the train station from night theater, and they were

―― 大興奮、大絶賛で、車の中が喧(やかま)しいことこの上もない

"So excited, so raving, so bustling in the car."

という状況でした。

-----

娘は、「ゴールデンカムイ」の原作とアニメを両方見ているので、正しい評価ができていると思います。

My daughter has seen the original "Golden Kamuy" and the anime, so I think she is making the proper judgment.

(ちなみに、私は完了した原作と、現在までのアニメの全部を見ています)

(By the way, I have seen the completed original and all the animations.)

また、ネットを見るかぎり、世間の評判も、概ね「絶賛」の方向です。

Also, as far as the Internet is concerned, the public's opinion is generally in the direction of "rave reviews.

こういうことがあるから、「実写化」は ―― というか、「全ての創作」は『創作前』に否定すべきではないのです。

―― 油断していると、簡単に「原理主義者」になってしまうぞ

Because of this, "live-action" -- or rather, "all creation" -- should not be denied "before creation."

-----

問題は、この私にあります。

The problem lies with me here.

この絶望的な時間のない状況で、どうやって映画館に行く時間を捻出するかで、頭を抱えています。

With this desperate lack of time, I am trying to wrap my head around how to find the time to go to the cinema.

2024,江端さんの忘備録

原作者(権利者)と二次創作者(権利者)との関係において、また痛ましい事件(自死)が発生しました。

Another tragic incident (suicide) has occurred in the relationship between the original creator (rights holder) and the secondary creator (rights holder).

本件に関して、どちら側からも主張(弁護)することができますが、私はもう疲れました。

You can argue (defend) either side on this matter, but I am tired of it.

そして、この事件のトリガーとなったのは、またもやSNSです。

And it was again social networking sites that triggered this incident.

だから、私は、これからも、

So I will continue to say,

―― 「SNSでの誹謗中傷はやめよう」とは言う人はいても、「SNSを使うのをやめよう」と言う人はいない

"There are people who say, "Let's stop slandering people on SNS," but there are no people who say, "Let's stop using SNS."

と言い続けます。

大丈夫です。SNSなくても、生きていけます。

Don't worry, you can live without social networking.

「断捨離」ならぬ、「断SNS」やってみませんか。

Why don't you try "SNS" instead of "decluttering"?

まずは1ヶ月間だけでも。

Just for a month first.

2024,江端さんの忘備録

最近、私は、バリカンで髪の毛を切り落しているのですが、家族に評判が良くありません ―― このお話は、これまで何度もしましたが。

自分の髪型に1mmのこだわりもなく、ただ、うっとうしい髪をとっとと処分したいだけ人には、「バリカン」の利用をご提案したいと思います。

Recently, I have been cutting off my hair with clippers, which my family has not received well -- I have told this story many times before.

ただでさえ人相が悪いのに、ここに丸刈りが加わると、その(外観の)凶悪さがハンパではなくなるらしい、です。

The face of the man is bad enough, and when the round-cropped head is added to it, the evilness of the face (in appearance) is worse, it seems.

しかし、私は、髪の毛がちょっとでも長くなると、髪の毛が鬱陶しくなって、仕事や勉学に集中できなくなります。

However, when my hair gets even a bit longer, it becomes depressing, and I find it hard to concentrate on my work or studies.

1000円理髪店(最近は値上りしていますが)は、10分で理髪してくれますが、その待ち時間は大体その3倍、下手すると1時間以上に達することがあります。

A 1,000-yen barbershop (although prices have increased recently) will give you a haircut in 10 minutes, but the wait time is usually three times that and can reach an hour or more if you are not careful.

これでは、「時短理髪」の意味がありません。

This makes "short haircuts" meaningless.

-----

そこで、先日、普通のはさみで、自分の髪を切り落としました。

So, the other day, I cut off my hair with regular scissors.

フローは以下の通りです。

The flow is as follows

(1)はさみを用意する

(1) Prepare scissors

(2)風呂場で全裸になる

(2) Naked in the bathroom

(3)鏡を見ながら髪の毛を切る(後ろの部分は、推測にながら切る)

(3) Cut hair while looking in the mirror (cut the back part while guessing)

(4)風呂場の髪の毛を、手で集めてビニール袋に入れて、ゴミ箱に放り込む。

(4) Collect hair in the bathroom by hand, put it in a plastic bag, and throw it in the trash.

(5)全身をシャワーで洗い流す

(5) Shower the entire body.

以上

the end

-----

判断基準は『嫁さんに、セルフ理髪がバレるか否か』でしたが ―― 簡単にバレました。

The criterion was "whether or not my wife would find out about my self-hairdressing" -- and she quickly did.

『耳元がハゲになっている』との指摘を受けましたが、それは1週間ガマンすれば自動的に復元します。

He pointed out that 'the area around the ear is bald,' but that will automatically be restored if you hold off for a week.

そもそも、今の私は、一週間に2回しか外出していません(会社と大学、それぞれ1回づつ)ので、あまり気にしないことにしています。

To begin with, I only go out twice a week now (once at work and once at college, respectively), so I don't worry too much about it.

-----

私の容姿は年齢と反比例して、どんどん「安く」なってきています。

My appearance is inversely proportional to my age, and I am getting "cheaper" by the minute.

安いデジタル腕時計、Amazonで購入したスラックス、100円老眼鏡、そして、自力理髪・・・

A cheap digital watch, slacks purchased from Amazon, 100 yen reading glasses, and a self-haircut...

これは、これからの私の人生において『外観でアピールする場面がない』ということの証拠だと思います(残っているのは、リアタイア後の再就職面接くらいかな)

This proves that 'there are no situations in my life from now on that will appeal to me in appearance' (I think the only thing left is a re-employment interview after I retire).

-----

まあ、そういう訳で、私の理髪が変なことになっていたとしても、皆さんが、それに触れなければ良いだけのことです。

That is why, even if my hairdressing is strange, you don't have to tell me about it.

これまで通り、よろしくお願い致します。

Thank you for your continued support.

2024,江端さんの忘備録

私の人生では、「鏡を見る」という習慣がありませんでした。

In my life, I have never had the habit of "looking in the mirror."

ところが、リモート会議で、カメラの位置を調整する為に、自分の姿を写すようになり、気持ちに変化が起きました。

However, my feelings changed when I started to take pictures of myself in remote meetings to adjust the camera's position.

―― というか、愕然としました

-- or rather, I was astonished.

『カメラに映る自分というのは、かくも見苦しいものなのか』と実感しました。

I realized how unsightly I was on camera.

----

リモートで化粧の加工をする技術があれば売れるかなぁ、とか考えていたのですが、

I was thinking that if I had the technology to process makeup remotely, I could sell it,

そんなものは、10年も前に開発されており、今、Zoomに実装されていることも確認しました("化粧"ではなく"フィルター"と称呼されているようですが)。

I have confirmed that such a thing was developed ten years ago and is now implemented in Zoom (although it seems to be called "filter" instead of "makeup").

まあ、そりゃそうですよね。顔のデジタル処理サービスは、リモート会議の基本中の基本ですよね。

Well, that's right. Face digital processing services are the foundation of remote conferencing, aren't they?

そもそも、他人の顔でリモート会議に参加できる技術があるのに、化粧の画像処理ができない訳がない。

To begin with, there is no reason why the technology that allows you to participate in a remote meeting with someone else's face can't be used to process makeup images.

-----

一部のイスラム文化や地域において、結婚初夜まで新婦の顔を見られないという風習があるようです。

In some Islamic cultures and regions, it is customary not to see the bride's face until the wedding night.

これからは、これに準ずる形の結婚が多くなるかもしれません。

In the future, we may see more and more marriages of a similar form.

さらに進んで、入籍だけして、生涯リモートだけで過す「リモート婚」という新しい婚姻形態が発生するのも「あり」と思います。

I think it would be "possible" to go further and create a new type of marriage called "remote marriage," in which a couple only registers and lives remotely for the rest of their lives.

我が国では、結婚の態様に制約がないので、相続や年金の受給、配偶者控除、医療費控除、在留資格、財産分与や年金分割などの、法的メリット"だけ"を受けることが可能になります。

In Japan, there are no restrictions on the type of marriage, which allows the couple to receive "only" legal benefits such as inheritance, pension benefits, spousal exemption, medical expense deduction, residency status, property division, and pension division.

逆に、このようなアプローチで、既存の「異性婚(*)」のみを前提とするシステムの崩壊を目指す、というのも面白いかもしれません。

Conversely, it would be interesting to see how such an approach could be used to disrupt the existing system of exclusively "heterosexual marriage(*).

■「高齢者を組織のトップから、ナチュラルに排除」する技術

-----

(*)ちなみに、ChatGPTに「同性婚の反意語は、異性婚でしょうか」と質問したら、『違う』と言われました。興味のある人は調べてみて下さい。

(*) By the way, I asked ChatGPT if the antonym of same-sex marriage is heterosexual marriage, and I was told 'no.' If you are interested, please look it up.

2024,江端さんの技術メモ

import asyncio
from fastapi import FastAPI

app = FastAPI()

async def hello_timer():
    while True:
        print("hello")
        await asyncio.sleep(10)  # 10秒待機

@app.on_event("startup")
async def startup_event():
    # FastAPIアプリケーションの起動時にタイマーを開始
    asyncio.create_task(hello_timer())

@app.get("/")
async def get_hello():
    return {"message": "hello"}

2024,江端さんの技術メモ

/*
C:\Users\ebata\yamaguchi\src_light

■このサンプルプログラムのキモ
(1)座標を入力して一番近いpostGISのノードIDを検知して、ダイクストラ計算を行うこと
(2)ユーザ心理を計算するファジィ推論を行うこと
が入っているプログラムである。

■自動計算を実施するバッチ、dummy.batの内容は以下の通り
go run light-sim.go data/mod_20220522holyday_visitor.csv data/new_20220522holyday_visitor.csv
go run light-sim.go data/mod_20220521holyday_visitor.csv data/new_20220521holyday_visitor.csv
go run light-sim.go data/mod_20220522holyday.csv data/new_20220522holyday.csv
go run light-sim.go data/mod_20220518weekday.csv data/new_20220518weekday.csv

■その入力用のcsvファイルの一つである、"data/mod_20220522holyday_visitor.csv"の内容は以下の通り
34.172891,131.456346,34.182418,131.474987,21
34.163801,131.444142,34.164454,131.449142,62
34.158856,131.435881,34.164727,131.431189,52
(中略)
34.146351,131.461154,34.167045,131.448468,20
34.145639,131.449237,34.149603,131.432828,29

■"data/bike_stations.csv"の中身
index,station,address,lat,lon,initial_stock,max_stock
1,null,null,34.102543,131.392639,20,20
2,null,null,34.102543,131.392639,20,20
3,null,null,34.102543,131.392639,20,20
4,山口県庁前バス停,山口市春日町2086-4,34.183621,131.471688,20,20
5,null,null,34.102543,131.392639,20,20
6,山口市役所 駐輪場,山口市亀山町2-1,34.178056,131.474204,20,20
7,一の坂川交通交流広場,山口市中河原7-1,34.17960577,131.4783006,20,20
8,null,null,34.102543,131.392639,20,20
9,コープやまぐちこことどうもん店 駐輪場,山口市道場門前1-1-18,34.174234,131.474885,20,20
10,山口駅 駐輪場,山口市惣太夫町288-9,34.172219,131.480013,20,20
11,山口市教育委員会 駐輪場,山口市中央5-14-22,34.170346,131.46915,20,20
12,null,null,34.102543,131.392639,20,20
13,ファミリーマート山口泉都町店,山口市泉都町9-2,34.167346,131.462048,20,20
14,防長苑,山口市熊野町4-29,34.167204,131.45973,20,20
15,null,null,34.102543,131.392639,20,20
16,ホテルニュータナカ,山口市湯田温泉2-6-24,34.163937,131.456115,20,20
17,null,null,34.102543,131.392639,20,20
18,湯田温泉駅 駐輪場,山口市今井町146-6,34.159954,131.459967,20,20
19,アルク平川店,山口市平井724-1,34.152742,131.464199,20,20
20,山口大学(正門),山口市吉田1677-1,34.149852,131.466214,20,20
21,小郡総合支所 駐輪場,山口市小郡下郷609番地1,34.102543,131.392639,20,20
22,KDDI維新ホール 駐輪場,山口市小郡令和1丁目1番地,34.09368,131.394,20,20
23,風の並木通り(新山口駅南口側),山口市小郡金町1-1付近,34.092322,131.397667,20,20
24,平成公園 駐車場内,山口市小郡平成町3-1,34.088168,131.401698,20,20
25,null,null,34.102543,131.392639,20,20
26,アルク小郡店,山口市小郡下郷2273番地1,34.097135,131.391295,20,20
27,null,null,34.102543,131.392639,20,20


*/
package main

import (
	"database/sql"
	"encoding/csv"
	"fmt"
	"log"

	"os"
	"strconv"

	_ "github.com/lib/pq"
)

// 自転車の型
type BikeParam struct {
	id            int // 自転車の識別番号
	destinationId int // 出発座標番号(*1)
	arrivalId     int // 到着座標番号

	// (*1)
	// 名前がdestination(目的地)となっているのは、バスがユーザを迎えに行き、載せた時点が「出発」扱いであった名残。
	// 自転車の場合は迎えに行く動作が無いので、名称変更が望ましい。

}

// 座標情報
type LocInfo struct {
	Lng    float64 // 経度
	Lat    float64 // 緯度
	Source int     // 地図DBのID
}

// 自転車の拠点
type BikeStation struct {
	location     LocInfo // 拠点の位置
	initialStock int     // 最初に配置する自転車の台数
	stationName  string  // 拠点の名称
}

// ステーションからの自転車の出入り
type StationStock struct {
	outgoing int
	incoming int
}

var stationstock [40]StationStock

const STATIONS_PATH string = "data/bike_stations.csv"

// 拠点情報を読み込み、BikeStation型の配列を返す
func getStationInfo(dbMap *sql.DB) []BikeStation {
	// ファイルをオープン
	csvFile, err := os.Open(STATIONS_PATH)
	if err != nil {
		log.Fatal(err)
	}
	defer csvFile.Close()

	// CSVファイルの中身を読み込み
	r := csv.NewReader(csvFile)
	rows, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	stationInfo := []BikeStation{} // 出力変数

	// 行ごとに
	for i, row := range rows {
		if i == 0 {
			continue // CSVのヘッダー行を無視
		}
		lat, err := strconv.ParseFloat(row[3], 64)
		if err != nil {
			log.Fatal(err)
		}
		lng, err := strconv.ParseFloat(row[4], 64)
		if err != nil {
			log.Fatal(err)
		}
		initialStockVal, err := strconv.Atoi(row[5])
		if err != nil {
			log.Fatal(err)
		}
		stationNameVal := row[1]

		// CSV読み込み時点の座標のログ(地図DBによって補正する前の座標)
		//log.Println("csv read result:", lng, lat, initialStock)

		// 地図DBを参照することによって、ステーションの位置をノードの位置にする
		source, lngModified, latModified := fixPosition(dbMap, lng, lat)
		loc := LocInfo{
			Lng:    lngModified,
			Lat:    latModified,
			Source: source,
		}

		// 読み込めていることの確認ログ
		//log.Println("Station", (i - 1), lngModified, latModified, initialStock, source)

		bikeStation := BikeStation{
			location:     loc,
			initialStock: initialStockVal,
			stationName:  stationNameVal,
		}
		stationInfo = append(stationInfo, bikeStation)
	}
	return stationInfo
}

// Scan用の仮変数
var source int
var longitude float64
var latitude float64
var dist float64

// 指定した座標に近いDB上の座標を取得
func fixPosition(db *sql.DB, _x1, _y1 float64) (int, float64, float64) {

	upperLimitMeter := 1500.0 // 近傍ノードの上限を1500 mに設定
	str := fmt.Sprintf(
		// 修正前: ways (道) の中から最近傍を取得
		// "SELECT source, x1 AS longitude, y1 AS latitude, ST_Distance('SRID=4326;POINT(%v %v)'::GEOGRAPHY, the_geom) AS dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%v %v)'), %.1f) ORDER BY dist LIMIT 1",
		// 修正後: ways_vertices_pgr (点座標) の中から最近傍を取得
		"SELECT id AS source, lon AS longitude, lat AS latitude, ST_Distance('SRID=4326;POINT(%v %v)'::GEOGRAPHY, the_geom) AS dist FROM ways_vertices_pgr WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%v %v)'), %.1f) ORDER BY dist LIMIT 1",
		_x1, _y1, _x1, _y1, upperLimitMeter,
	)

	//fmt.Println(str)

	rows, err := db.Query(str)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	foundGoodMapNode := false

	for rows.Next() {
		foundGoodMapNode = true
		if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
			fmt.Println(err)
		}
		//fmt.Println(source, longitude, latitude, dist)
	}

	if !foundGoodMapNode {
		log.Println("Warning: in func fixPosition: Good Map Node not found for query point (",
			_x1, ",", _y1, ")")
	}

	return source, longitude, latitude
}

/*
func getShortestDistanceToBikeStation(dbMap *sql.DB, node int) int {

	StationId := -1
	distance := 1000000.0

	for i := 0; i < 2; i++ {
		rowsDijkstra, errDijkstra := dbMap.Query(
			"SELECT seq,source, target, x1, y1, x2, y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length_m as cost FROM ways', $1::bigint , $2::bigint , directed:=false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq",
			node,

			Station[i].Node)
		if errDijkstra != nil {
			log.Fatal(errDijkstra)
		}
		defer rowsDijkstra.Close()

		var agg_cost float64

		for rowsDijkstra.Next() {
			var x1, y1, x2, y2 float64
			var seq, source, target int

			err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost)
			if err != nil {
				fmt.Println(err)
			}
		}

		if distance > agg_cost {
			distance = agg_cost
			StationId = i
		}
	}
	return StationId
}
*/

// 江端修正版
func getDijkstraPath(dbMap *sql.DB, locInfoStart, locInfoGoal LocInfo) ([]LocInfo, float64) {
	//log.Println("getDijkstraPath", locInfoStart, locInfoGoal)

	var path []LocInfo // 経路 (返り値の一つ目)
	var totalDistanceKm float64

	rowsDijkstra, errDijkstra := dbMap.Query(
		"SELECT seq,source, target, x1, y1, x2, y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length_m as cost FROM ways', $1::bigint , $2::bigint , directed:=false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq",
		locInfoStart.Source,
		locInfoGoal.Source)

	if errDijkstra != nil {
		log.Fatal(errDijkstra)
	}
	defer rowsDijkstra.Close()

	var agg_cost float64

	isFirstCheck := true
	isSourceCheck := true

	for rowsDijkstra.Next() {

		var x1, y1, x2, y2 float64

		var seq int
		var target int

		// まずnodeを読む
		if err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost); err != nil {
			fmt.Println(err)
		}

		// 最初の1回だけ入る
		if isFirstCheck {
			if source == locInfoStart.Source {
				isSourceCheck = true
			} else {
				isSourceCheck = false
			}
			isFirstCheck = false
		}

		var loc LocInfo

		if isSourceCheck {
			loc.Source = source
			loc.Lng = x1
			loc.Lat = y1
		} else {
			loc.Source = target
			loc.Lng = x2
			loc.Lat = y2
		}

		loc.Source = target

		path = append(path, loc)
	}

	// ラストノードだけは手入力
	path = append(path, locInfoGoal)

	totalDistanceKm = agg_cost / 1000.0
	return path, totalDistanceKm
}

// 一番近いステーションのIDを取得
func getNearestStation(dbMap *sql.DB, stationInfo []BikeStation, queryLocation LocInfo) (int, float64) {
	bestDistanceKm := 1000.0 // 十分に大きい数
	var bestStationId int
	for i := 0; i < len(stationInfo); i++ {

		// ダイクストラ法による経路で決定する距離
		// SQLクエリを繰り返し実行するため、処理が遅くなる可能性がある
		_, distKm := getDijkstraPath(dbMap, queryLocation, stationInfo[i].location)

		// 直線距離の概算値(代替の計算方法)
		//distKm, _ := distanceKm(queryLocation.Lng, queryLocation.Lat,
		//	stationInfo[i].location.Lng, stationInfo[i].location.Lat)

		//log.Println("to station", i, "distanceKm=", distKm)

		if distKm < bestDistanceKm {
			bestDistanceKm = distKm
			bestStationId = i
		}
	}
	return bestStationId, bestDistanceKm
}

func main() {

	dbMap, err := sql.Open("postgres",
		"user=postgres password=password host=192.168.0.23 port=15432 dbname=yama_db sslmode=disable")
	log.Println("------------------ map db open ------------------")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer dbMap.Close()

	// バイクステーション情報の読み込み
	stationInfo := getStationInfo(dbMap)

	//fmt.Println(stationInfo)

	//file2, err2 := os.Open("data/new_20220518weekday.csv")
	file2, err2 := os.Open(os.Args[1])
	if err2 != nil {
		log.Fatal(err2)
	}
	defer file2.Close()

	r2 := csv.NewReader(file2)
	rows2, err2 := r2.ReadAll() // csvを一度に全て読み込む
	if err != nil {
		log.Fatal(err2)
	}

	//file3, err3 := os.Create("data/calc2_new_20220518weekday.csv") // 第2パラメータ
	file3, err3 := os.Create(os.Args[2]) // 第2パラメータ
	if err3 != nil {
		panic(err)
	}
	w := csv.NewWriter(file3)

	output := []string{"id", "age", "origin_loc_Lng", "origin_loc_Lat", "dest_loc_Lng", "dest_loc_Lat", "distance_from_origin", "distance_between_stations", "distance_to_dest", "complain"}

	if err = w.Write(output); err != nil {
		log.Fatal(err)
	}

	for id, row := range rows2 {

		/*
			origin_lng := 131.4686813247102
			origin_lat := 34.17901518198008

			dest_lng := 131.45836175237153
			dest_lat := 34.160484344205294
		*/

		origin_lng, err := strconv.ParseFloat(row[1], 64)
		if err != nil {
			log.Fatal(err)
		}
		origin_lat, err := strconv.ParseFloat(row[0], 64)
		if err != nil {
			log.Fatal(err)
		}

		dest_lng, err := strconv.ParseFloat(row[3], 64)
		if err != nil {
			log.Fatal(err)
		}

		dest_lat, err := strconv.ParseFloat(row[2], 64)
		if err != nil {
			log.Fatal(err)
		}

		age, err := strconv.ParseFloat(row[4], 64) // 年齢も実数扱いする
		if err != nil {
			log.Fatal(err)
		}

		// 最接近のステーションを選ぶ

		var origin_loc, dest_loc LocInfo

		origin_loc.Source, origin_loc.Lng, origin_loc.Lat = fixPosition(dbMap, origin_lng, origin_lat)
		dest_loc.Source, dest_loc.Lng, dest_loc.Lat = fixPosition(dbMap, dest_lng, dest_lat)

		stationId_from_origin, distance_from_origin := getNearestStation(dbMap, stationInfo, origin_loc) // Originから最初のステーション
		stationId_to_dest, distance_to_dest := getNearestStation(dbMap, stationInfo, dest_loc)           // 最後のステーションからDest

		fmt.Println(stationInfo[stationId_from_origin])
		stationstock[stationId_from_origin].outgoing++

		fmt.Println(stationInfo[stationId_to_dest])
		stationstock[stationId_to_dest].incoming++

		_, distance_between_stations := getDijkstraPath(dbMap, stationInfo[stationId_from_origin].location, stationInfo[stationId_to_dest].location)
		_, shortest_distance_total := getDijkstraPath(dbMap, origin_loc, dest_loc)

		fmt.Println("stationId_from_origin, distance_from_origin", stationId_from_origin, distance_from_origin)
		fmt.Println("stationId_to_dest, distance_to_dest", stationId_to_dest, distance_to_dest)
		fmt.Println("distance_between_stations", distance_between_stations)
		fmt.Println("shortest_distance_total", shortest_distance_total)

		// 出発 ― _x[km]の歩行 ― 最初のステーション ― _y[km]の自転車走行 ― 最後のステーション ― _[km]の歩行 ―  到着
		complain := fuzzy_reasoning(distance_from_origin, distance_between_stations, distance_to_dest, age)
		fmt.Println("complain", complain)

		output := []string{
			fmt.Sprint(id),
			fmt.Sprint(age),
			fmt.Sprint(origin_loc.Lng),
			fmt.Sprint(origin_loc.Lat),
			fmt.Sprint(dest_loc.Lng),
			fmt.Sprint(dest_loc.Lat),
			fmt.Sprint(distance_from_origin),
			fmt.Sprint(distance_between_stations),
			fmt.Sprint(distance_to_dest),
			fmt.Sprint(complain)}

		fmt.Println(output)

		if err = w.Write(output); err != nil {
			log.Fatal(err)
		}
	}

	// test
	output = []string{"stationid", "outgoing", "incoming"}
	if err = w.Write(output); err != nil {
		log.Fatal(err)
	}

	for i := 0; i < 40; i++ {
		output = []string{fmt.Sprint(i + 1), fmt.Sprint(stationstock[i].outgoing), fmt.Sprint(stationstock[i].incoming)}
		// i+1しているのは、1からスタートする為
		if err = w.Write(output); err != nil {
			log.Fatal(err)
		}
	}

	defer w.Flush()

	if err := w.Error(); err != nil {
		log.Fatal(err)
	}
}

func fuzzy_reasoning(distance_from_origin, distance_between_stations, distance_to_dest, age float64) float64 {

	////// パラメータの作成

	// 絶対的歩行距離
	walk := min_2(distance_from_origin, distance_to_dest)
	// 総体的自転車移動距離
	ratio := distance_between_stations / (distance_from_origin + distance_between_stations + distance_to_dest)
	// 絶対的自転車移動距離
	bike := distance_between_stations

	// Age(前件部)
	Age_Less := new_condition_MF3(45, 20, "LESS")
	Age_Common := new_condition_MF3(45, 20, "COMMON") // 中央が 42歳
	Age_More := new_condition_MF3(45, 20, "MORE")

	// 絶対的歩行距離(前件部)
	Walk_Less := new_condition_MF3(0.4, 0.1, "LESS")
	Walk_Common := new_condition_MF3(0.4, 0.1, "COMMON")
	Walk_More := new_condition_MF3(0.4, 0.1, "MORE")

	// 相対的自転車移動比率(former)
	Bike_Ratio_Less := new_condition_MF3(0.7, 0.1, "LESS")
	Bike_Ratio_Common := new_condition_MF3(0.7, 0.1, "COMMON")
	Bike_Ratio_More := new_condition_MF3(0.7, 0.1, "MORE")

	// 絶対的自転車距離(前件部)
	Bike_Less := new_condition_MF3(1.5, 1.0, "LESS")
	Bike_Common := new_condition_MF3(1.5, 1.0, "COMMON")
	Bike_More := new_condition_MF3(1.5, 1.0, "MORE")

	// Complain(後件部)
	Complain_LessLess := new_action_MF5(0.5, 0.25, "LESSLESS")
	Complain_Less := new_action_MF5(0.5, 0.25, "LESS")
	Complain_Common := new_action_MF5(0.5, 0.25, "COMMON")
	Complain_More := new_action_MF5(0.5, 0.25, "MORE")
	Complain_MoreMore := new_action_MF5(0.5, 0.25, "MOREMORE")

	// [Rule A00]
	Rule_A00 := min_2(Age_Less.func_X(age), Walk_Less.func_X(walk))
	Complain_LessLess.func_Max(Rule_A00)
	//fmt.Println("Rule_A00", Rule_A00)

	// [Rule A01]
	Rule_A01 := min_2(Age_Less.func_X(age), Walk_Common.func_X(walk))
	Complain_Less.func_Max(Rule_A01)
	//fmt.Println("Rule_A01", Rule_A01)

	// [Rule A02]
	Rule_A02 := min_2(Age_Less.func_X(age), Walk_More.func_X(walk))
	Complain_Common.func_Max(Rule_A02)
	//fmt.Println("Rule_A02", Rule_A02)

	// [Rule A10]
	Rule_A10 := min_2(Age_Common.func_X(age), Walk_Less.func_X(walk))
	Complain_Common.func_Max(Rule_A10)
	//fmt.Println("Rule_A10", Rule_A10)

	// [Rule A11]
	Rule_A11 := min_2(Age_Common.func_X(age), Walk_Common.func_X(walk))
	Complain_Common.func_Max(Rule_A11)
	//fmt.Println("Rule_A11", Rule_A11)

	// [Rule A12]
	Rule_A12 := min_2(Age_Common.func_X(age), Walk_More.func_X(walk))
	Complain_More.func_Max(Rule_A12)
	//fmt.Println("Rule_A12", Rule_A12)

	// [Rule A20]
	Rule_A20 := min_2(Age_More.func_X(age), Walk_Less.func_X(walk))
	Complain_Common.func_Max(Rule_A20)
	//fmt.Println("Rule_A20", Rule_A20)

	// [Rule A21]
	Rule_A21 := min_2(Age_More.func_X(age), Walk_Common.func_X(walk))
	Complain_More.func_Max(Rule_A21)
	//fmt.Println("Rule_A21", Rule_A21)

	// [Rule A22]
	Rule_A22 := min_2(Age_More.func_X(age), Walk_More.func_X(walk))
	Complain_MoreMore.func_Max(Rule_A22)
	//fmt.Println("Rule_A22", Rule_A22)

	// [Rule B00]
	Rule_B00 := Bike_Ratio_Less.func_X(ratio)
	Complain_MoreMore.func_Max(Rule_B00)
	//fmt.Println("Rule_B00", Rule_B00)

	// [Rule B01]
	Rule_B01 := Bike_Ratio_Common.func_X(ratio)
	Complain_Common.func_Max(Rule_B01)
	//fmt.Println("Rule_B01", Rule_B01)

	// [Rule B02]
	Rule_B02 := Bike_Ratio_More.func_X(ratio)
	Complain_LessLess.func_Max(Rule_B02)
	//fmt.Println("Rule_B02", Rule_B02)

	// [Rule C00]
	Rule_C00 := min_2(Age_Less.func_X(age), Bike_Less.func_X(bike))
	Complain_LessLess.func_Max(Rule_C00)
	//fmt.Println("Rule_C00", Rule_C00)

	// [Rule C01]
	Rule_C01 := min_2(Age_Less.func_X(age), Bike_Common.func_X(bike))
	Complain_LessLess.func_Max(Rule_C01)
	//fmt.Println("Rule_C01", Rule_C01)

	// [Rule C02]
	Rule_C02 := min_2(Age_Less.func_X(age), Bike_More.func_X(bike))
	Complain_LessLess.func_Max(Rule_C02)
	//fmt.Println("Rule_C02", Rule_C02)

	// [Rule C10]
	Rule_C10 := min_2(Age_Common.func_X(age), Bike_Less.func_X(bike))
	Complain_Less.func_Max(Rule_C10)
	//fmt.Println("Rule_C10", Rule_C10)

	// [Rule C11]
	Rule_C11 := min_2(Age_Common.func_X(age), Bike_Common.func_X(bike))
	Complain_Common.func_Max(Rule_C11)
	//fmt.Println("Rule_C11", Rule_C11)

	// [Rule C12]
	Rule_C12 := min_2(Age_Common.func_X(age), Bike_More.func_X(bike))
	Complain_More.func_Max(Rule_C12)
	//fmt.Println("Rule_C12", Rule_C12)

	// [Rule C20]
	Rule_C20 := min_2(Age_More.func_X(age), Bike_Less.func_X(bike))
	Complain_Common.func_Max(Rule_C20)
	//fmt.Println("Rule_C20", Rule_C20)

	// [Rule C21]
	Rule_C21 := min_2(Age_More.func_X(age), Bike_Common.func_X(bike))
	Complain_More.func_Max(Rule_C21)
	//fmt.Println("Rule_C21", Rule_C21)

	// [Rule C22]
	Rule_C22 := min_2(Age_More.func_X(age), Bike_More.func_X(bike))
	Complain_MoreMore.func_Max(Rule_C22)
	//fmt.Println("Rule_C22", Rule_C22)

	// Reasoning calculations
	numerator :=
		Complain_LessLess.func_X()*Complain_LessLess.func_Y() +
			Complain_Less.func_X()*Complain_Less.func_Y() +
			Complain_Common.func_X()*Complain_Common.func_Y() +
			Complain_More.func_X()*Complain_More.func_Y() +
			Complain_MoreMore.func_X()*Complain_MoreMore.func_Y()

	denominator :=
		Complain_LessLess.func_Y() +
			Complain_Less.func_Y() +
			Complain_Common.func_Y() +
			Complain_More.func_Y() +
			Complain_MoreMore.func_Y()

	reasoning := numerator / denominator

	return reasoning

}

func max_2(a, b float64) float64 {
	if a > b {
		return a
	} else {
		return b
	}
}

func min_2(a, b float64) float64 {
	if a > b {
		return b
	} else {
		return a
	}
}

type condition_MF3 struct { // Base class for condition_MF3
	center  float64
	width   float64
	express string
}

func new_condition_MF3(_center, _width float64, _express string) *condition_MF3 {
	c3 := new(condition_MF3)
	c3.center = _center
	c3.width = _width
	c3.express = _express
	return c3
}

// Class for the membership function (3 mountains) of the former case
func (c3 *condition_MF3) func_X(_x float64) float64 {
	// x,y denote coordinates on the membership function
	x := _x
	y := 0.0 // The value of y is always greater than or equal to 0 and less than or equal to 1

	if c3.express == "LESS" {
		if x <= c3.center-c3.width {
			y = 1.0
		} else if x <= c3.center {
			y = -1.0 / c3.width * (x - c3.center)
		} else {
			y = 0.0
		}
	} else if c3.express == "COMMON" {
		if x <= c3.center-c3.width {
			y = 0.0
		} else if x <= c3.center {
			y = 1.0/c3.width*(x-c3.center) + 1.0
		} else if x <= c3.center+c3.width {
			y = -1.0/c3.width*(x-c3.center) + 1.0
		} else {
			y = 0.0
		}
	} else if c3.express == "MORE" {
		if x <= c3.center {
			y = 0.0
		} else if x <= c3.center+c3.width {
			y = 1.0 / c3.width * (x - c3.center)
		} else {
			y = 1.0
		}
	} else {
		fmt.Println("MF3: wrong expression")
		os.Exit(1)
	}
	return y
}

type condition_MF5 struct { // Base class for condition_MF5
	center  float64
	width   float64
	express string
}

func new_condition_MF5(_center, _width float64, _express string) *condition_MF5 {
	c5 := new(condition_MF5)
	c5.center = _center
	c5.width = _width
	c5.express = _express
	return c5
}

func (c5 *condition_MF5) func_X(_x float64) float64 {
	// Class for the former membership function (5 mountains)
	// x,y are the coordinates on the membership function

	x := _x
	y := 0.0 // The value of y is always greater than or equal to 0 and less than or equal to 1

	if c5.express == "LESSLESS" {
		if x <= c5.center-2.0*c5.width {
			y = 1.0
		} else if x <= c5.center-c5.width {
			y = -1.0/c5.width*(x-(c5.center-2.0*c5.width)) + 1.0
		} else {
			y = 0.0
		}
	} else if c5.express == "LESS" {
		if x <= c5.center-2.0*c5.width {
			y = 0.0
		} else if x <= c5.center-c5.width {
			y = 1.0/c5.width*(x-(c5.center-c5.width)) + 1.0
		} else if x <= c5.center {
			y = -1.0/c5.width*(x-(c5.center-c5.width)) + 1.0
		} else {
			y = 0.0
		}
	} else if c5.express == "COMMON" {
		if x <= c5.center-c5.width {
			y = 0.0
		} else if x <= c5.center {
			y = 1.0/c5.width*(x-c5.center) + 1.0
		} else if x <= c5.center+c5.width {
			y = -1.0/c5.width*(x-c5.center) + 1.0
		} else {
			y = 0.0
		}
	} else if c5.express == "MORE" {
		if x <= c5.center {
			y = 0.0
		} else if x <= c5.center+c5.width {
			y = 1.0/c5.width*(x-(c5.center+c5.width)) + 1.0
		} else if x <= c5.center+2.0*c5.width {
			y = -1.0/c5.width*(x-(c5.center+c5.width)) + 1.0
		} else {
			y = 0.0
		}
	} else if c5.express == "MOREMORE" {
		if x <= c5.center+c5.width {
			y = 0.0
		} else if x <= c5.center+2.0*c5.width {
			y = 1.0/c5.width*(x-(c5.center+2.0*c5.width)) + 1.0
		} else {
			y = 1.0
		}
	} else {
		fmt.Println("MF5 func_X(): wrong expression")
		os.Exit(1)
	}

	return y
}

/////////////////////////////

type action_MF5 struct { // Base class for action_MF5
	center  float64
	width   float64
	express string
	x       float64
	y       float64
}

type action_MF3 struct { // Base class for action_MF3
	center  float64
	width   float64
	express string
	x       float64
	y       float64
}

func new_action_MF5(_center, _width float64, _express string) *action_MF5 {
	a5 := new(action_MF5)
	a5.center = _center
	a5.width = _width
	a5.express = _express

	if a5.express == "LESSLESS" {
		a5.x = a5.center - 2.0*a5.width
	} else if a5.express == "LESS" {
		a5.x = a5.center - a5.width
	} else if a5.express == "COMMON" {
		a5.x = a5.center
	} else if a5.express == "MORE" {
		a5.x = a5.center + a5.width
	} else if a5.express == "MOREMORE" {
		a5.x = a5.center + 2.0*a5.width
	} else {
		fmt.Println("new_action_MF5: wrong scale expression")
		os.Exit(-1)
	}

	a5.y = 0.0

	return a5
}

func new_action_MF3(_center, _width float64, _express string) *action_MF3 {
	a3 := new(action_MF3)
	a3.center = _center
	a3.width = _width
	a3.express = _express

	if a3.express == "LESS" {
		a3.x = a3.center - a3.width
	} else if a3.express == "COMMON" {
		a3.x = a3.center
	} else if a3.express == "MORE" {
		a3.x = a3.center + a3.width
	} else {
		fmt.Println("new_action_MF3: wrong scale expression")
		os.Exit(-1)
	}

	a3.y = 0.0

	return a3
}

// The latter membership function (5 mountains) class
func (a5 *action_MF5) func_Y() float64 {
	return a5.y
}

// The latter membership function (3 mountains) class
func (a3 *action_MF3) func_Y() float64 {
	return a3.y
}

func (a5 *action_MF5) func_Max(b float64) {
	a5.y = max_2(b, a5.y)
}

func (a3 *action_MF3) func_Max(b float64) {
	a3.y = max_2(b, a3.y)
}

func (a5 *action_MF5) func_X() float64 {
	return a5.x
}

func (a3 *action_MF3) func_X() float64 {
	return a3.x
}