File size: 6,687 Bytes
9fa2182
 
 
 
 
a2492c3
9fa2182
b955f86
9fa2182
967d63f
84b46ac
9fa2182
09714d6
 
 
 
 
 
 
 
 
9fa2182
 
0cd448a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd28328
 
 
 
 
 
 
 
 
a2492c3
fd28328
 
 
 
 
 
 
 
 
 
 
06338fc
 
 
 
fd28328
 
 
 
 
 
 
a2492c3
 
fd28328
a2492c3
 
 
fd28328
a2492c3
 
0cd448a
 
a2492c3
 
 
 
 
fd28328
 
 
b955f86
 
 
 
 
0cd448a
 
519fcb9
a206d6a
9fa2182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a206d6a
9fa2182
b955f86
 
 
 
a2492c3
b955f86
 
 
a2492c3
b955f86
 
0cd448a
9fa2182
519fcb9
 
 
09714d6
9fa2182
 
 
a2492c3
 
 
 
 
b955f86
a2492c3
b955f86
 
 
a2492c3
b955f86
a2492c3
b955f86
a2492c3
b955f86
 
 
a2492c3
b955f86
a2492c3
b955f86
a2492c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28639ea
 
 
 
 
 
 
 
 
b955f86
06338fc
 
 
 
a2492c3
b955f86
a2492c3
 
0cd448a
a2492c3
 
0cd448a
b955f86
84b46ac
a2492c3
28639ea
 
 
 
84b46ac
28639ea
a2492c3
b955f86
 
0f7799e
 
 
b955f86
 
a2492c3
28639ea
0f7799e
b955f86
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import multiprocessing as mp
import os
import tempfile
import time
from pathlib import Path
from typing import Callable

import numpy as np
import pandas as pd
from data import generate_data, read_csv
from plots import plot_predictions


def empty_df():
    return pd.DataFrame(
        {
            "Equation": [],
            "Loss": [],
            "Complexity": [],
        }
    )


def pysr_fit(queue: mp.Queue, out_queue: mp.Queue):
    import pysr

    while True:
        # Get the arguments from the queue, if available
        args = queue.get()
        if args is None:
            break
        X = args["X"]
        y = args["y"]
        kwargs = args["kwargs"]
        model = pysr.PySRRegressor(
            progress=False,
            timeout_in_seconds=1000,
            **kwargs,
        )
        model.fit(X, y)
        out_queue.put(None)


def pysr_predict(queue: mp.Queue, out_queue: mp.Queue):
    while True:
        args = queue.get()

        if args is None:
            break

        X = args["X"]
        equation_file = str(args["equation_file"])
        index = args["index"]

        equation_file_pkl = equation_file.replace(".csv", ".pkl")
        equation_file_bkup = equation_file + ".bkup"

        equation_file_copy = equation_file.replace(".csv", "_copy.csv")
        equation_file_pkl_copy = equation_file.replace(".csv", "_copy.pkl")

        # TODO: See if there is way to get lock on file
        os.system(f"cp {equation_file_bkup} {equation_file_copy}")
        os.system(f"cp {equation_file_pkl} {equation_file_pkl_copy}")

        # Note that we import pysr late in this process to avoid
        # pre-compiling the code in two places at once
        import pysr

        try:
            model = pysr.PySRRegressor.from_file(equation_file_pkl_copy, verbosity=0)
        except pd.errors.EmptyDataError:
            continue

        ypred = model.predict(X, index)

        # Rename the columns to uppercase
        equations = model.equations_[["complexity", "loss", "equation"]].copy()

        # Remove any row that has worse loss than previous row:
        equations = equations[equations["loss"].cummin() == equations["loss"]]
        # TODO: Why is this needed? Are rows not being removed?

        equations.columns = ["Complexity", "Loss", "Equation"]
        out_queue.put(dict(ypred=ypred, equations=equations))


class ProcessWrapper:
    def __init__(self, target: Callable[[mp.Queue, mp.Queue], None]):
        self.queue = mp.Queue(maxsize=1)
        self.out_queue = mp.Queue(maxsize=1)
        self.process = mp.Process(target=target, args=(self.queue, self.out_queue))
        self.process.start()


ACTIVE_PROCESS = None


def _random_string():
    return "".join(list(np.random.choice("abcdefghijklmnopqrstuvwxyz".split(), 16)))


def processing(
    *,
    file_input,
    force_run,
    test_equation,
    num_points,
    noise_level,
    data_seed,
    niterations,
    maxsize,
    binary_operators,
    unary_operators,
    plot_update_delay,
    parsimony,
    populations,
    population_size,
    ncycles_per_iteration,
    elementwise_loss,
    adaptive_parsimony_scaling,
    optimizer_algorithm,
    optimizer_iterations,
    batching,
    batch_size,
    **kwargs,
):
    # random string:
    global ACTIVE_PROCESS
    cur_process = _random_string()
    ACTIVE_PROCESS = cur_process

    """Load data, then spawn a process to run the greet function."""
    print("Starting PySR fit process")
    writer = ProcessWrapper(pysr_fit)

    print("Starting PySR predict process")
    reader = ProcessWrapper(pysr_predict)

    if file_input is not None:
        try:
            X, y = read_csv(file_input, force_run)
        except ValueError as e:
            return (empty_df(), plot_predictions([], []), str(e))
    else:
        X, y = generate_data(test_equation, num_points, noise_level, data_seed)

    tmpdirname = tempfile.mkdtemp()
    base = Path(tmpdirname)
    equation_file = base / "hall_of_fame.csv"
    # Check if queue is empty, if not, kill the process
    # and start a new one
    if not writer.queue.empty():
        print("Restarting PySR fit process")
        if writer.process.is_alive():
            writer.process.terminate()
            writer.process.join()

        writer = ProcessWrapper(pysr_fit)

    if not reader.queue.empty():
        print("Restarting PySR predict process")
        if reader.process.is_alive():
            reader.process.terminate()
            reader.process.join()

        reader = ProcessWrapper(pysr_predict)

    writer.queue.put(
        dict(
            X=X,
            y=y,
            kwargs=dict(
                niterations=niterations,
                maxsize=maxsize,
                binary_operators=binary_operators,
                unary_operators=unary_operators,
                equation_file=equation_file,
                parsimony=parsimony,
                populations=populations,
                population_size=population_size,
                ncycles_per_iteration=ncycles_per_iteration,
                elementwise_loss=elementwise_loss,
                adaptive_parsimony_scaling=adaptive_parsimony_scaling,
                optimizer_algorithm=optimizer_algorithm,
                optimizer_iterations=optimizer_iterations,
                batching=batching,
                batch_size=batch_size,
            ),
        )
    )

    last_yield = (
        pd.DataFrame({"Complexity": [], "Loss": [], "Equation": []}),
        plot_predictions([], []),
        "Started!",
    )

    yield last_yield

    while writer.out_queue.empty():
        if (
            equation_file.exists()
            and Path(str(equation_file).replace(".csv", ".pkl")).exists()
        ):
            # First, copy the file to a the copy file
            reader.queue.put(
                dict(
                    X=X,
                    equation_file=equation_file,
                    index=-1,
                )
            )
            out = reader.out_queue.get()
            predictions = out["ypred"]
            equations = out["equations"]
            last_yield = (
                equations[["Complexity", "Loss", "Equation"]],
                plot_predictions(y, predictions),
                "Running...",
            )
            yield last_yield

        if cur_process != ACTIVE_PROCESS:
            # Kill both reader and writer
            writer.process.kill()
            reader.process.kill()
            yield (*last_yield[:-1], "Stopped.")
            return

        time.sleep(0.1)

    yield (*last_yield[:-1], "Done.")
    return


def stop():
    global ACTIVE_PROCESS
    ACTIVE_PROCESS = None
    return