Module nkcs.main

Main script for starting NKCS coevolutionary experiments.

Expand source code
#!/usr/bin/python3
#
# Copyright (C) 2019--2024 Richard Preen <rpreen@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""Main script for starting NKCS coevolutionary experiments."""

from __future__ import annotations

from typing import Final

import dill
import numpy as np
from tqdm import tqdm

from .constants import Constants as Cons  # parameters are in constants.py
from .constants import cons_to_string
from .ea import EA
from .nkcs import NKCS
from .perf import save_data
from .plot import plot

if __name__ == "__main__":
    # results storage
    n_res: Final[int] = Cons.F * Cons.E
    evals: np.ndarray = np.zeros((n_res, Cons.G))
    perf_best: np.ndarray = np.zeros((n_res, Cons.G))
    perf_avg: np.ndarray = np.zeros((n_res, Cons.G))

    r: int = 0  # run counter
    nkcs: list[NKCS] = []  # NKCS landscapes
    ea: list[EA] = []  # EA populations

    if Cons.EXPERIMENT_LOAD:  # reuse fitness landscapes and initial populations
        with open("experiment.pkl", "rb") as fp:
            ea = dill.load(fp)
            nkcs = dill.load(fp)
        if len(nkcs) != Cons.F or len(ea) != n_res:
            raise Exception("loaded experiment does not match constants")
        for _ in range(Cons.F):
            for _ in range(Cons.E):
                ea[r].update_perf(evals[r], perf_best[r], perf_avg[r])
                r += 1
    else:  # create new fitness landscapes and initial populations
        for f in range(Cons.F):
            nkcs.append(NKCS())
            for _ in range(Cons.E):
                ea.append(EA())
                ea[r].run_initial(nkcs[f])
                ea[r].update_perf(evals[r], perf_best[r], perf_avg[r])
                r += 1

    if Cons.EXPERIMENT_SAVE:  # save initial populations
        with open("experiment.pkl", "wb") as fp:
            dill.dump(ea, fp)

    # run the experiments
    r = 0
    bar = tqdm(total=n_res)  # progress bar
    for f in range(Cons.F):  # F NKCS functions
        for e in range(Cons.E):  # E experiments
            if Cons.ACQUISITION == "ea":
                ea[r].run_ea(nkcs[f], evals[r], perf_best[r], perf_avg[r])
            else:
                ea[r].run_sea(nkcs[f], evals[r], perf_best[r], perf_avg[r])
            best_fit: float = ea[r].get_best_fit(0)
            status = f"nkcs {f} experiment {e} complete: {best_fit:.5f}"
            r += 1
            bar.set_description(status)
            bar.refresh()
            bar.update(1)
    bar.close()

    if Cons.EXPERIMENT_SAVE:  # save fitness landscapes
        with open("experiment.pkl", "a+b") as fp:
            dill.dump(nkcs, fp)

    # write performance to a file and plot results
    filename: Final[str] = cons_to_string()
    save_data(filename, evals, perf_best, perf_avg)
    if Cons.PLOT:
        plot([filename], filename)