流れ動作、作業の自動化

こんにちは mtjです。

作業の自動化という話は昔から依頼あるのですが それには様々な分析が必要になります。

まず人が判断している 作業にどのような判断が存在するかの分析が必要になります。
そしてそれをソフトに入れる事が可能かを判断します。
人がノリで右か左を押します のような案件はソフトでは難しいです。
ソフトでは最終的には0か1かが必要になります。

それを人が判断している場合は 判断している要件をまとめて センサーなりなんなりで0,1の判断できる状態まで落とし込む必要があります。
完全自動は上記をすべてデジタルに落とし込める段階になっていれば可能です。
判断が難しい箇所等は外部から人が操作する等で半自動化で進めます。

極端ではありますが1日一回人がボタンを押す半自動化か、その押す動作をどれだけの費用かけて完全自動化するか
という話が自動化、DX化には常に付きまといます。

基本は費用と時間があれば 根本的に不可能な物でない限り可能です。
現実は期間も予算もあるのでその中で可能な限り 効果のあるシステム、仕様を提案するのが自分たちの仕事になります。
何かの本でプログラマーには政治も大事との話がありますが 本当に大事だと感じます。

Anacondaのアップデートが進まなくなった場合の対処法

Pythonの仮想環境構築にはAnacondaを使用しています。
Pythonも新しいバージョンではswitch文が使用できたり型ヒントの機能が拡張されていたりと
色々便利になっていそうなので、Anacondaで使用できるバージョンを上げようと思いました。
しかしNavigator右上の更新ボタンから更新を実行しても、
‘updating package on root…’の状態から全く更新が進みません。

調べてみると、Anacondaを更新できないのはよくあることのようで、
中にはアンインストール&再インストールする人もいるようなのですが、
自分はGithubで見つけた方法[1]を、Navigatorは管理者権限で起動し
以下のように行うことで更新できました。
(管理者権限なしだとコマンドの実行時に更新失敗しました)

0. Navigatorを管理者権限で起動
1. Enviromentsのbase(root)でターミナルを開き、次のコマンドをそれぞれ実行

conda install anaconda=custom
conda update conda

2. Navigatorを管理者権限で再起動
3. 更新ボタンから更新実行

再インストールの場合は環境のバックアップが必要で面倒[2]なので、回避できて良かったです。
以上です。

[1] Navigator update to 2.1.0 from 2.0.4 never finishes · Issue #12643 · ContinuumIO/anaconda-issues · GitHub

[2] 環境のバックアップファイル内にはcondaのみでなくpipのパッケージも記載されるため、
pipを使用した環境であってもバックアップファイルを作成するだけでバックアップ可能であり、
かつ全環境の一括バックアップは公式では未サポートですが有志のスクリプトにより実行可能なので、
大変とまでは行かないのですが、とは言え成功したか確認が必要なこともあり手間がかかります。

他社設備のPCソフトを更新

検査設備などの他社作成PCソフトだけを更新したいというご要望がよくあります。
更新したい理由は色々で、最新のOSやPCに更新したい・機能追加をしたい・品質が低いので作り直したいなど。

大概の場合、元ソフトのソースコードが無いので、仕様書や電気図面などを参考にしたり、元ソフトを色々いじって目でコピーすることなります。

特に問題になるのが設備と独自の通信を行っている場合です。
一般的な通信インターフェイス(RS232C・TCP・GPIBなど)で設備と通信している場合は、通信を中継するソフトを作成して通信内容を調査すればなんとか対応可能です。
特殊なドライバ経由で通信を行っている場合は一番大変。途中のDLLを中継して調べられればラッキーですが、簡単にはいかないですね。
それでも色々な方法を駆使して調べますけど。

他のソフト会社は、このような案件はリスクが高いので引き受けてもくれなかったり、高額だったりするでしょうが、私はこのような案件が大好きですので、ご要望があればご連絡おまちしております。

自動化、省力化

こんにちは mtj です。

工場等では昔から進んでいる自動化、省力化 最近はレストランでも進んでいますね
普段の店で見るのであればセルフレジ、配膳ロボットでしょうか

猫ロボットは動いているとなかなかかわいかったです、普段自分たちが良く見るのはあれの下だけのパターンなので容姿があると印象が結構変わりました。
猫ロボットは配膳だけですけど行き来の回数を考えると効果はかなりの物になると思いました

やはり省力化はこういった細かい事から行う方が導入のハードルは低いんだなと感じました

工場等でこういった提案をするとよくあるのが 人のほうが安い、全部自動化しないと意味がない等言われることがあります。
全部自動化を猫ロボットで考えた場合 料理を自分で乗せて、自動的に判断して目的の場所へ移動、空の食器等は片付ける等でしょうか
面白さはありますが導入ハードルも開発期間もとてつもないでしょう

今後も労働人口が少なくなるに連れ省力化に取り組んでこなかった企業が淘汰される時代になると思います
プログラム開発もそうで 効率の良いIDE、ライブラリ等、テストの省力化等を使う能力が大事になってくると思っています
またシステムの提案の考え方も変わってくると思います

開発手法でもそういった方法を逃さず手に入れるようにしていきたいですね

C#でライフゲーム

今日はブログで書くネタがなかったので、C#でライフゲームを実装してみました。
コードは末尾に記載します。
※コンソールのカーソルを非表示にし忘れていました。コードでは修正済みです。

ライフゲームとは1970年に考案された
古典的かつシンプルな人工生命シミュレーションプログラムです。
ライフゲーム – Wikipedia
格子状に配置したセルに、生存と死亡の2つの状態を割り当て、各セルについて、
「現在のセルの状態及び周囲8セルの状態から次の状態を求めて、状態を更新する(※)」
を繰り返すことで、生命の生と死のようなサイクルをシミュレーションします。
※状態遷移は以下
・死亡かつ周囲生存3セル→生存(”誕生”)
・生存かつ周囲生存1セル以下→死亡(”過疎”)
・生存かつ周囲生存2セルor3セル→生存(”生存”)
・生存かつ周囲生存4セル以上→死亡(”過密”)

基本的にはどこかで変化が生じなくなりシミュレーションが止まるのですが、
中には変化をし続ける初期セルのパターンもあり、
現在でも愛好家により新しいパターンが発見されているようです。

近年の進化したAIに比べると随分とシンプルですが、単調過ぎず見ていて面白いですよね。
(若い頃に知って感動した故にそう感じるのかもしれませんが。。。)

10年ほど前、学部3年生の頃にゼミの課題で実装したことがありましたが、
(そのゼミは4年に上がる前に教授が他所に移ってしまったため消滅しました。。。)
当時はプログラミングを講義で習って1年ちょっとの段階だったのもあり、
動作はしたもののそれは酷いコードを書いていた覚えがあります。
(言語はほぼCの機能しか使用していない、なんちゃってC++で、描画はOpenGLで行っていました)

当時に比べると洗練されたプログラムを書けるようになったと思います。
継続は力なりということにして締めさせていただきます。

以上、老いを感じる、思い出語りを含んだ記事更新でした。

 // Program.cs
using LifeGame;

new Controller().Simulate(20, 0);
 // Lifegame.cs
using System.Diagnostics;
using System.Text;

namespace LifeGame;

/// <summary> モデルクラス </summary>
internal class Model {
    /// <summary> グリッド一辺サイズ </summary>
    public int Size { get; private set; } = 0;
    /// <summary> 現在の各セルの状態 </summary>
    public bool[,] Cells { get; private set; } = new bool[0, 0];
    /// <summary> 更新用のセル状態バッファ(兼以前の各セルの状態) </summary>
    private bool[,] _cells { get; set; } = new bool[0, 0];

    /// <summary> 初期化します。 </summary>
    public void Init(int size, int seed) {
        Size = size;
        Cells = new bool[Size, Size];
        _cells = new bool[Size, Size];
        var rnd = new Random(seed);
        for (var x = 0; x < Size; x++) {
            for (var y = 0; y < Size; y++) {
                // 乱数で初期状態設定
                Cells[x, y] = rnd.Next(0, 100) % 2 == 1;
            }
        }
    }

    /// <summary> 終端の状態か。 </summary>
    public bool IsTerminal() {
        for (var x = 0; x < Size; x++) {
            for (var y = 0; y < Size; y++) {
                var diff = Cells[x, y] != _cells[x, y];
                if (diff)
                    return false; // 差分あれば非終端
            }
        }
        return true; // 差分なしで終端
    }

    /// <summary> 更新します。 </summary>
    public void Update() {
        for (var x = 0; x < Size; x++) {
            for (var y = 0; y < Size; y++) {
                _cells[x, y] = GetNextState(x, y); // 次状態取得
            }
        }
        (Cells, _cells) = (_cells, Cells); // 更新した状態と現在の状態を交換
    }

    /// <summary> 次の状態を取得します。 </summary>
    private bool GetNextState(int x, int y) {
        var isAlive = Cells[x, y];
        var countAroundAlive = CountAroundAliveCells(x, y);
        return (isAlive, countAroundAlive) switch {
            (false, 3) => true, // 誕生
            (false, _) => false, // (死亡維持)
            (true, <= 1) => false, // 過疎
            (true, 2 or 3) => true, // 生存
            (true, >= 4) => false, // 過密
        };
    }

    /// <summary> グリッド走査用 x座標オフセット </summary>
    private static int[] Dx { get; } = { -1, 0, 1, -1, 1, -1, 0, 1 };
    /// <summary> グリッド走査用 y座標オフセット </summary>
    private static int[] Dy { get; } = { -1, -1, -1, 0, 0, 1, 1, 1 };

    /// <summary> 指定した中心セルの周囲の生存セルを数えます。 </summary>
    private int CountAroundAliveCells(int cx, int cy) {
        var count = 0;
        foreach (var (dx, dy) in Dx.Zip(Dy)) {
            // 中心座標+オフセットを正規化して座標算出
            var (x, y) = (ValidateIndex(cx + dx), ValidateIndex(cy + dy));
            if (Cells[x, y])
                count++; // 生存ならカウントアップ
        }
        return count;
    }

    /// <summary> セルのインデックスを正規化します。端と端が繋がるように。 </summary>
    private int ValidateIndex(int index) => index switch {
        < 0 => Size - 1, // 左の外側なら右端に
        _ when Size <= index => 0, // 右の外側なら左端に
        _ => index // 範囲内はそのまま
    };
}

/// <summary> ビュークラス </summary>
internal class View {

    /// <summary> 初期化します。 </summary>
    public void Init(Model model) {
        Console.Clear();
        Console.CursorVisible = false;
        Console.ForegroundColor = ConsoleColor.Green;
        Draw(model);
    }

    /// <summary> 終了します。 </summary>
    public void Term() {
        Console.ForegroundColor = ConsoleColor.Gray;
        Console.CursorVisible = true;
        Console.WriteLine("Done.");
    }

    /// <summary> 描画します。 </summary>
    public void Draw(Model model) {
        Console.SetCursorPosition(0, 0); // コンソール上書きのためカーソルを左上に
        var sb = new StringBuilder();
        for (var x = 0; x < model.Size; x++) {
            for (var y = 0; y < model.Size; y++) {
                var state = model.Cells[x, y] ? "■" : "_"; // 状態を文字化
                sb.Append(state);
            }
            sb.AppendLine();
        }
        Console.Write(sb.ToString()); // 出力
    }
}

/// <summary> 制御クラス </summary>
internal class Controller {
    /// <summary> モデル </summary>
    private Model Model { get; } = new();
    /// <summary> ビュー </summary>
    private View View { get; } = new();

    /// <summary> シミュレーションを実行します。 </summary>
    public void Simulate(int size, int seed) {
        var sw = new Stopwatch();
        Model.Init(size, seed);
        View.Init(Model);
        const int frameMs = 50; // 1フレーム50ms (20FPS)
        while (!Model.IsTerminal()) {
            // 更新所要時間を1フレーム時間から引いた上で待機
            Thread.Sleep(Math.Max(1, frameMs - (int)sw.ElapsedMilliseconds));
            sw.Restart();
            Model.Update(); // モデル更新
            View.Draw(Model); // 描画更新
        }
        View.Term();
    }
}