def test_pandamodels_dev_mode(): 
    
    from julia import Main
    from julia import Pkg
    from julia import Base

    if Base.find_package("PandaModels"):  
        # remove PandaModels to reinstall it
        Pkg.rm("PandaModels")
        Pkg.resolve()
        
    Pkg.Registry.update()
    Pkg.add("PandaModels")  
    print("installing dev mode is a slow process!")  
    Pkg.resolve()
    Pkg.develop("PandaModels")
    # add pandamodels dependencies: slow process
    Pkg.instantiate()            
    Pkg.build()
    Pkg.resolve()
    print("dev mode of PandaModels is added to julia packages")

    try:
        Pkg.activate("PandaModels")
        Main.using("PandaModels")
        print("using PandaModels in its dev mode!")
    except ImportError:
        # assert False
        raise ImportError("cannot use PandaModels in its dev mode")
      
    # activate julia base mode
    Pkg.activate()
    Pkg.free("PandaModels")
    Pkg.resolve()
示例#2
0
def _call_pandamodels(buffer_file, julia_file, dev_mode):  # pragma: no cover

    try:
        import julia
        from julia import Main
        from julia import Pkg
        from julia import Base
    except ImportError:
        raise ImportError(
            "Please install pyjulia properly to run pandapower with PandaModels.jl."
        )

    try:
        julia.Julia()
    except:
        raise UserWarning(
            "Could not connect to julia, please check that Julia is installed and pyjulia is correctly configured"
        )

    if not Base.find_package("PandaModels"):
        logger.info(
            "PandaModels.jl is not installed in julia. It is added now!")
        Pkg.Registry.update()
        Pkg.add("PandaModels")

        if dev_mode:
            logger.info("installing dev mode is a slow process!")
            Pkg.resolve()
            Pkg.develop("PandaModels")
            # add pandamodels dependencies: slow process
            Pkg.instantiate()

        Pkg.build()
        Pkg.resolve()
        logger.info("Successfully added PandaModels")

    if dev_mode:
        Pkg.develop("PandaModels")
        Pkg.build()
        Pkg.resolve()
        Pkg.activate("PandaModels")

    try:
        Main.using("PandaModels")
    except ImportError:
        raise ImportError("cannot use PandaModels")

    Main.buffer_file = buffer_file
    result_pm = Main.eval(julia_file + "(buffer_file)")

    # if dev_mode:
    #     Pkg.activate()
    #     Pkg.free("PandaModels")
    #     Pkg.resolve()
    return result_pm
示例#3
0
文件: sr.py 项目: MilesCranmer/PySR
def install(julia_project=None):  # pragma: no cover
    import julia

    julia.install()

    julia_project = _get_julia_project(julia_project)

    init_julia()
    from julia import Pkg

    Pkg.activate(f"{_escape_filename(julia_project)}")
    Pkg.update()
    Pkg.instantiate()
    Pkg.precompile()
    warnings.warn(
        "It is recommended to restart Python after installing PySR's dependencies,"
        " so that the Julia environment is properly initialized.")
def run_jump_model(self, dfm, data, run_uuid, bau=False):
    profiler = Profiler()
    time_dict = dict()
    name = 'reopt' if not bau else 'reopt_bau'
    reopt_inputs = dfm['reopt_inputs'] if not bau else dfm['reopt_inputs_bau']
    self.data = data
    self.run_uuid = data['outputs']['Scenario']['run_uuid']
    self.user_uuid = data['outputs']['Scenario'].get('user_uuid')

    if platform.system() == "Darwin":
        ext = ".dylib"
    elif platform.system() == "Windows":
        ext = ".dll"
    else:
        ext = ".so"  # if platform.system() == "Linux":
    julia_img_file = os.path.join("julia_envs", "Xpress",
                                  "JuliaXpressSysimage" + ext)

    logger.info("Running JuMP model ...")
    try:
        if os.path.isfile(julia_img_file):
            # TODO: clean up this try/except block
            logger.info("Found Julia image file {}.".format(julia_img_file))
            t_start = time.time()
            api = LibJulia.load()
            api.sysimage = julia_img_file
            api.init_julia()
            from julia import Main
            time_dict["pyjulia_start_seconds"] = time.time() - t_start
        else:
            t_start = time.time()
            j = julia.Julia()
            from julia import Main
            time_dict["pyjulia_start_seconds"] = time.time() - t_start

        t_start = time.time()
        Main.using("Pkg")
        from julia import Pkg
        time_dict["pyjulia_pkg_seconds"] = time.time() - t_start

        if os.environ.get("SOLVER") == "xpress":
            t_start = time.time()
            Pkg.activate("./julia_envs/Xpress/")
            time_dict["pyjulia_activate_seconds"] = time.time() - t_start

            try:
                t_start = time.time()
                Main.include("reo/src/reopt_xpress_model.jl")
                time_dict["pyjulia_include_model_seconds"] = time.time(
                ) - t_start

            except ImportError:
                # should only need to instantiate once
                Pkg.instantiate()
                Main.include("reo/src/reopt_xpress_model.jl")

            t_start = time.time()
            if bau:
                model = Main.reopt_model(
                    float(data["inputs"]["Scenario"]["timeout_seconds"]),
                    float(data["inputs"]["Scenario"]
                          ["optimality_tolerance_bau"]))
            else:
                model = Main.reopt_model(
                    float(data["inputs"]["Scenario"]["timeout_seconds"]),
                    float(data["inputs"]["Scenario"]
                          ["optimality_tolerance_techs"]))
            time_dict["pyjulia_make_model_seconds"] = time.time() - t_start

        elif os.environ.get("SOLVER") == "cbc":
            t_start = time.time()
            Pkg.activate("./julia_envs/Cbc/")
            time_dict["pyjulia_activate_seconds"] = time.time() - t_start

            t_start = time.time()
            Main.include("reo/src/reopt_cbc_model.jl")
            time_dict["pyjulia_include_model_seconds"] = time.time() - t_start

            t_start = time.time()
            model = Main.reopt_model(
                float(data["inputs"]["Scenario"]["timeout_seconds"]),
                float(data["inputs"]["Scenario"]["optimality_tolerance_bau"]))
            time_dict["pyjulia_make_model_seconds"] = time.time() - t_start

        elif os.environ.get("SOLVER") == "scip":
            t_start = time.time()
            Pkg.activate("./julia_envs/SCIP/")
            time_dict["pyjulia_activate_seconds"] = time.time() - t_start

            t_start = time.time()
            Main.include("reo/src/reopt_scip_model.jl")
            time_dict["pyjulia_include_model_seconds"] = time.time() - t_start

            t_start = time.time()
            model = Main.reopt_model(
                float(data["inputs"]["Scenario"]["timeout_seconds"]),
                float(data["inputs"]["Scenario"]["optimality_tolerance_bau"]))
            time_dict["pyjulia_make_model_seconds"] = time.time() - t_start

        else:
            raise REoptFailedToStartError(
                message=
                "The environment variable SOLVER must be set to one of [xpress, cbc, scip].",
                run_uuid=self.run_uuid,
                user_uuid=self.user_uuid)

        if bau or not data["inputs"]["Scenario"]["use_decomposition_model"]:
            t_start = time.time()
            Main.include("reo/src/reopt.jl")
            time_dict["pyjulia_include_reopt_seconds"] = time.time() - t_start

            t_start = time.time()
            results = Main.reopt(model, reopt_inputs)
            time_dict["pyjulia_run_reopt_seconds"] = time.time() - t_start
        else:
            t_start = time.time()
            Main.include("reo/src/reopt_decomposed.jl")
            time_dict["pyjulia_include_reopt_seconds"] = time.time() - t_start

            t_start = time.time()
            results = run_decomposed_model(data, model, reopt_inputs)
            time_dict["pyjulia_run_reopt_seconds"] = time.time() - t_start

        results = scrub_numpy_arrays_from_dict(results)
        results.update(time_dict)

    except Exception as e:
        if isinstance(e, REoptFailedToStartError):
            raise e
        elif "DimensionMismatch" in e.args[
                0]:  # JuMP may mishandle a timeout when no feasible solution is returned
            msg = "Optimization exceeded timeout: {} seconds.".format(
                data["inputs"]["Scenario"]["timeout_seconds"])
            logger.info(msg)
            raise OptimizationTimeout(task=name,
                                      message=msg,
                                      run_uuid=self.run_uuid,
                                      user_uuid=self.user_uuid)
        exc_type, exc_value, exc_traceback = sys.exc_info()
        print(exc_type)
        print(exc_value)
        print(exc_traceback)
        logger.error("REopt.py raise unexpected error: UUID: " +
                     str(self.run_uuid))
        raise UnexpectedError(exc_type,
                              exc_value,
                              traceback.format_tb(exc_traceback),
                              task=name,
                              run_uuid=self.run_uuid,
                              user_uuid=self.user_uuid)
    else:
        status = results["status"]
        logger.info("REopt run successful. Status {}".format(status))
        if bau:
            dfm['results_bau'] = results  # will be flat dict
        else:
            dfm['results'] = results

        if status.strip().lower() == 'timed-out':
            msg = "Optimization exceeded timeout: {} seconds.".format(
                data["inputs"]["Scenario"]["timeout_seconds"])
            logger.info(msg)
            raise OptimizationTimeout(task=name,
                                      message=msg,
                                      run_uuid=self.run_uuid,
                                      user_uuid=self.user_uuid)
        elif status.strip().lower() != 'optimal':
            logger.error(
                "REopt status not optimal. Raising NotOptimal Exception.")
            raise NotOptimal(task=name,
                             run_uuid=self.run_uuid,
                             status=status.strip(),
                             user_uuid=self.user_uuid)

    profiler.profileEnd()
    ModelManager.updateModel('ProfileModel',
                             {name + '_seconds': profiler.getDuration()},
                             run_uuid)

    # reduce the amount data being transferred between tasks
    if bau:
        del dfm['reopt_inputs_bau']
    else:
        del dfm['reopt_inputs']
    return dfm
示例#5
0
文件: sr.py 项目: MilesCranmer/PySR
def pysr(
    X,
    y,
    weights=None,
    binary_operators=None,
    unary_operators=None,
    procs=cpu_count(),
    loss="L2DistLoss()",
    populations=20,
    niterations=100,
    ncyclesperiteration=300,
    alpha=0.1,
    annealing=False,
    fractionReplaced=0.10,
    fractionReplacedHof=0.10,
    npop=1000,
    parsimony=1e-4,
    migration=True,
    hofMigration=True,
    shouldOptimizeConstants=True,
    topn=10,
    weightAddNode=1,
    weightInsertNode=3,
    weightDeleteNode=3,
    weightDoNothing=1,
    weightMutateConstant=10,
    weightMutateOperator=1,
    weightRandomize=1,
    weightSimplify=0.002,
    perturbationFactor=1.0,
    extra_sympy_mappings=None,
    extra_torch_mappings=None,
    extra_jax_mappings=None,
    equation_file=None,
    verbosity=1e9,
    progress=None,
    maxsize=20,
    fast_cycle=False,
    maxdepth=None,
    variable_names=None,
    batching=False,
    batchSize=50,
    select_k_features=None,
    warmupMaxsizeBy=0.0,
    constraints=None,
    useFrequency=True,
    tempdir=None,
    delete_tempfiles=True,
    julia_project=None,
    update=True,
    temp_equation_file=False,
    output_jax_format=False,
    output_torch_format=False,
    optimizer_algorithm="BFGS",
    optimizer_nrestarts=3,
    optimize_probability=1.0,
    optimizer_iterations=10,
    tournament_selection_n=10,
    tournament_selection_p=1.0,
    denoise=False,
    Xresampled=None,
    precision=32,
    multithreading=None,
    **kwargs,
):
    """Run symbolic regression to fit f(X[i, :]) ~ y[i] for all i.
    Note: most default parameters have been tuned over several example
    equations, but you should adjust `niterations`,
    `binary_operators`, `unary_operators` to your requirements.
    You can view more detailed explanations of the options on the
    [options page](https://pysr.readthedocs.io/en/latest/docs/options/) of the documentation.

    :param X: 2D array. Rows are examples, columns are features. If pandas DataFrame, the columns are used for variable names (so make sure they don't contain spaces).
    :type X: np.ndarray/pandas.DataFrame
    :param y: 1D array (rows are examples) or 2D array (rows are examples, columns are outputs). Putting in a 2D array will trigger a search for equations for each feature of y.
    :type y: np.ndarray
    :param weights: same shape as y. Each element is how to weight the mean-square-error loss for that particular element of y.
    :type weights: np.ndarray
    :param binary_operators: List of strings giving the binary operators in Julia's Base. Default is ["+", "-", "*", "/",].
    :type binary_operators: list
    :param unary_operators: Same but for operators taking a single scalar. Default is [].
    :type unary_operators: list
    :param procs: Number of processes (=number of populations running).
    :type procs: int
    :param loss: String of Julia code specifying the loss function.  Can either be a loss from LossFunctions.jl, or your own loss written as a function. Examples of custom written losses include: `myloss(x, y) = abs(x-y)` for non-weighted, or `myloss(x, y, w) = w*abs(x-y)` for weighted.  Among the included losses, these are as follows. Regression: `LPDistLoss{P}()`, `L1DistLoss()`, `L2DistLoss()` (mean square), `LogitDistLoss()`, `HuberLoss(d)`, `L1EpsilonInsLoss(ϵ)`, `L2EpsilonInsLoss(ϵ)`, `PeriodicLoss(c)`, `QuantileLoss(τ)`.  Classification: `ZeroOneLoss()`, `PerceptronLoss()`, `L1HingeLoss()`, `SmoothedL1HingeLoss(γ)`, `ModifiedHuberLoss()`, `L2MarginLoss()`, `ExpLoss()`, `SigmoidLoss()`, `DWDMarginLoss(q)`.
    :type loss: str
    :param populations: Number of populations running.
    :type populations: int
    :param niterations: Number of iterations of the algorithm to run. The best equations are printed, and migrate between populations, at the end of each.
    :type niterations: int
    :param ncyclesperiteration: Number of total mutations to run, per 10 samples of the population, per iteration.
    :type ncyclesperiteration: int
    :param alpha: Initial temperature.
    :type alpha: float
    :param annealing: Whether to use annealing. You should (and it is default).
    :type annealing: bool
    :param fractionReplaced: How much of population to replace with migrating equations from other populations.
    :type fractionReplaced: float
    :param fractionReplacedHof: How much of population to replace with migrating equations from hall of fame.
    :type fractionReplacedHof: float
    :param npop: Number of individuals in each population
    :type npop: int
    :param parsimony: Multiplicative factor for how much to punish complexity.
    :type parsimony: float
    :param migration: Whether to migrate.
    :type migration: bool
    :param hofMigration: Whether to have the hall of fame migrate.
    :type hofMigration: bool
    :param shouldOptimizeConstants: Whether to numerically optimize constants (Nelder-Mead/Newton) at the end of each iteration.
    :type shouldOptimizeConstants: bool
    :param topn: How many top individuals migrate from each population.
    :type topn: int
    :param perturbationFactor: Constants are perturbed by a max factor of (perturbationFactor*T + 1). Either multiplied by this or divided by this.
    :type perturbationFactor: float
    :param weightAddNode: Relative likelihood for mutation to add a node
    :type weightAddNode: float
    :param weightInsertNode: Relative likelihood for mutation to insert a node
    :type weightInsertNode: float
    :param weightDeleteNode: Relative likelihood for mutation to delete a node
    :type weightDeleteNode: float
    :param weightDoNothing: Relative likelihood for mutation to leave the individual
    :type weightDoNothing: float
    :param weightMutateConstant: Relative likelihood for mutation to change the constant slightly in a random direction.
    :type weightMutateConstant: float
    :param weightMutateOperator: Relative likelihood for mutation to swap an operator.
    :type weightMutateOperator: float
    :param weightRandomize: Relative likelihood for mutation to completely delete and then randomly generate the equation
    :type weightRandomize: float
    :param weightSimplify: Relative likelihood for mutation to simplify constant parts by evaluation
    :type weightSimplify: float
    :param equation_file: Where to save the files (.csv separated by |)
    :type equation_file: str
    :param verbosity: What verbosity level to use. 0 means minimal print statements.
    :type verbosity: int
    :param progress: Whether to use a progress bar instead of printing to stdout.
    :type progress: bool
    :param maxsize: Max size of an equation.
    :type maxsize: int
    :param maxdepth: Max depth of an equation. You can use both maxsize and maxdepth.  maxdepth is by default set to = maxsize, which means that it is redundant.
    :type maxdepth: int
    :param fast_cycle: (experimental) - batch over population subsamples. This is a slightly different algorithm than regularized evolution, but does cycles 15% faster. May be algorithmically less efficient.
    :type fast_cycle: bool
    :param variable_names: a list of names for the variables, other than "x0", "x1", etc.
    :type variable_names: list
    :param batching: whether to compare population members on small batches during evolution. Still uses full dataset for comparing against hall of fame.
    :type batching: bool
    :param batchSize: the amount of data to use if doing batching.
    :type batchSize: int
    :param select_k_features: whether to run feature selection in Python using random forests, before passing to the symbolic regression code. None means no feature selection; an int means select that many features.
    :type select_k_features: None/int
    :param warmupMaxsizeBy: whether to slowly increase max size from a small number up to the maxsize (if greater than 0).  If greater than 0, says the fraction of training time at which the current maxsize will reach the user-passed maxsize.
    :type warmupMaxsizeBy: float
    :param constraints: dictionary of int (unary) or 2-tuples (binary), this enforces maxsize constraints on the individual arguments of operators. E.g., `'pow': (-1, 1)` says that power laws can have any complexity left argument, but only 1 complexity exponent. Use this to force more interpretable solutions.
    :type constraints: dict
    :param useFrequency: whether to measure the frequency of complexities, and use that instead of parsimony to explore equation space. Will naturally find equations of all complexities.
    :type useFrequency: bool
    :param tempdir: directory for the temporary files
    :type tempdir: str/None
    :param delete_tempfiles: whether to delete the temporary files after finishing
    :type delete_tempfiles: bool
    :param julia_project: a Julia environment location containing a Project.toml (and potentially the source code for SymbolicRegression.jl).  Default gives the Python package directory, where a Project.toml file should be present from the install.
    :type julia_project: str/None
    :param update: Whether to automatically update Julia packages.
    :type update: bool
    :param temp_equation_file: Whether to put the hall of fame file in the temp directory. Deletion is then controlled with the delete_tempfiles argument.
    :type temp_equation_file: bool
    :param output_jax_format: Whether to create a 'jax_format' column in the output, containing jax-callable functions and the default parameters in a jax array.
    :type output_jax_format: bool
    :param output_torch_format: Whether to create a 'torch_format' column in the output, containing a torch module with trainable parameters.
    :type output_torch_format: bool
    :param tournament_selection_n: Number of expressions to consider in each tournament.
    :type tournament_selection_n: int
    :param tournament_selection_p: Probability of selecting the best expression in each tournament. The probability will decay as p*(1-p)^n for other expressions, sorted by loss.
    :type tournament_selection_p: float
    :param denoise: Whether to use a Gaussian Process to denoise the data before inputting to PySR. Can help PySR fit noisy data.
    :type denoise: bool
    :param precision: What precision to use for the data. By default this is 32 (float32), but you can select 64 or 16 as well.
    :type precision: int
    :param multithreading: Use multithreading instead of distributed backend. Default is yes. Using procs=0 will turn off both.
    :type multithreading: bool
    :param **kwargs: Other options passed to SymbolicRegression.Options, for example, if you modify SymbolicRegression.jl to include additional arguments.
    :type **kwargs: dict
    :returns: Results dataframe, giving complexity, MSE, and equations (as strings), as well as functional forms. If list, each element corresponds to a dataframe of equations for each output.
    :type: pd.DataFrame/list
    """
    global already_ran

    if binary_operators is None:
        binary_operators = "+ * - /".split(" ")
    if unary_operators is None:
        unary_operators = []
    if extra_sympy_mappings is None:
        extra_sympy_mappings = {}
    if variable_names is None:
        variable_names = []
    if constraints is None:
        constraints = {}
    if multithreading is None:
        # Default is multithreading=True, unless explicitly set,
        # or procs is set to 0 (serial mode).
        multithreading = procs != 0

    global Main
    if Main is None:
        if multithreading:
            os.environ["JULIA_NUM_THREADS"] = str(procs)

        Main = init_julia()

    buffer_available = "buffer" in sys.stdout.__dir__()

    if progress is not None:
        if progress and not buffer_available:
            warnings.warn(
                "Note: it looks like you are running in Jupyter. The progress bar will be turned off."
            )
            progress = False
    else:
        progress = buffer_available

    assert optimizer_algorithm in ["NelderMead", "BFGS"]
    assert tournament_selection_n < npop

    if isinstance(X, pd.DataFrame):
        variable_names = list(X.columns)
        X = np.array(X)

    if len(X.shape) == 1:
        X = X[:, None]

    assert not isinstance(y, pd.DataFrame)

    if len(variable_names) == 0:
        variable_names = [f"x{i}" for i in range(X.shape[1])]

    if extra_jax_mappings is not None:
        for value in extra_jax_mappings.values():
            if not isinstance(value, str):
                raise NotImplementedError(
                    "extra_jax_mappings must have keys that are strings! e.g., {sympy.sqrt: 'jnp.sqrt'}."
                )

    if extra_torch_mappings is not None:
        for value in extra_jax_mappings.values():
            if not callable(value):
                raise NotImplementedError(
                    "extra_torch_mappings must be callable functions! e.g., {sympy.sqrt: torch.sqrt}."
                )

    use_custom_variable_names = len(variable_names) != 0
    # TODO: this is always true.

    _check_assertions(
        X,
        binary_operators,
        unary_operators,
        use_custom_variable_names,
        variable_names,
        weights,
        y,
    )

    if len(X) > 10000 and not batching:
        warnings.warn(
            "Note: you are running with more than 10,000 datapoints. You should consider turning on batching (https://pysr.readthedocs.io/en/latest/docs/options/#batching). You should also reconsider if you need that many datapoints. Unless you have a large amount of noise (in which case you should smooth your dataset first), generally < 10,000 datapoints is enough to find a functional form with symbolic regression. More datapoints will lower the search speed."
        )

    if maxsize > 40:
        warnings.warn(
            "Note: Using a large maxsize for the equation search will be exponentially slower and use significant memory. You should consider turning `useFrequency` to False, and perhaps use `warmupMaxsizeBy`."
        )
    if maxsize < 7:
        raise NotImplementedError("PySR requires a maxsize of at least 7")

    X, selection = _handle_feature_selection(X, select_k_features, y,
                                             variable_names)

    if maxdepth is None:
        maxdepth = maxsize
    if isinstance(binary_operators, str):
        binary_operators = [binary_operators]
    if isinstance(unary_operators, str):
        unary_operators = [unary_operators]

    if len(y.shape) == 1 or (len(y.shape) == 2 and y.shape[1] == 1):
        multioutput = False
        nout = 1
        y = y.reshape(-1)
    elif len(y.shape) == 2:
        multioutput = True
        nout = y.shape[1]
    else:
        raise NotImplementedError("y shape not supported!")

    if denoise:
        if weights is not None:
            raise NotImplementedError(
                "No weights for denoising - the weights are learned.")
        if Xresampled is not None:
            # Select among only the selected features:
            if isinstance(Xresampled, pd.DataFrame):
                # Handle Xresampled is pandas dataframe
                if selection is not None:
                    Xresampled = Xresampled[[
                        variable_names[i] for i in selection
                    ]]
                else:
                    Xresampled = Xresampled[variable_names]
                Xresampled = np.array(Xresampled)
            else:
                if selection is not None:
                    Xresampled = Xresampled[:, selection]
        if multioutput:
            y = np.stack(
                [
                    _denoise(X, y[:, i], Xresampled=Xresampled)[1]
                    for i in range(nout)
                ],
                axis=1,
            )
            if Xresampled is not None:
                X = Xresampled
        else:
            X, y = _denoise(X, y, Xresampled=Xresampled)

    julia_project = _get_julia_project(julia_project)

    tmpdir = Path(tempfile.mkdtemp(dir=tempdir))

    if temp_equation_file:
        equation_file = tmpdir / "hall_of_fame.csv"
    elif equation_file is None:
        date_time = datetime.now().strftime("%Y-%m-%d_%H%M%S.%f")[:-3]
        equation_file = "hall_of_fame_" + date_time + ".csv"

    _create_inline_operators(binary_operators=binary_operators,
                             unary_operators=unary_operators)
    _handle_constraints(
        binary_operators=binary_operators,
        unary_operators=unary_operators,
        constraints=constraints,
    )

    una_constraints = [constraints[op] for op in unary_operators]
    bin_constraints = [constraints[op] for op in binary_operators]

    try:
        # TODO: is this needed since Julia now prints directly to stdout?
        term_width = shutil.get_terminal_size().columns
    except:
        _, term_width = subprocess.check_output(["stty", "size"]).split()

    if not already_ran:
        from julia import Pkg

        Pkg.activate(f"{_escape_filename(julia_project)}")
        try:
            if update:
                Pkg.resolve()
                Pkg.instantiate()
            else:
                Pkg.instantiate()
        except RuntimeError as e:
            raise ImportError(f"""
Required dependencies are not installed or built.  Run the following code in the Python REPL:

    >>> import pysr
    >>> pysr.install()
    
Tried to activate project {julia_project} but failed.""") from e
        Main.eval("using SymbolicRegression")

        Main.plus = Main.eval("(+)")
        Main.sub = Main.eval("(-)")
        Main.mult = Main.eval("(*)")
        Main.pow = Main.eval("(^)")
        Main.div = Main.eval("(/)")

    Main.custom_loss = Main.eval(loss)

    mutationWeights = [
        float(weightMutateConstant),
        float(weightMutateOperator),
        float(weightAddNode),
        float(weightInsertNode),
        float(weightDeleteNode),
        float(weightSimplify),
        float(weightRandomize),
        float(weightDoNothing),
    ]

    options = Main.Options(
        binary_operators=Main.eval(
            str(tuple(binary_operators)).replace("'", "")),
        unary_operators=Main.eval(
            str(tuple(unary_operators)).replace("'", "")),
        bin_constraints=bin_constraints,
        una_constraints=una_constraints,
        parsimony=float(parsimony),
        loss=Main.custom_loss,
        alpha=float(alpha),
        maxsize=int(maxsize),
        maxdepth=int(maxdepth),
        fast_cycle=fast_cycle,
        migration=migration,
        hofMigration=hofMigration,
        fractionReplacedHof=float(fractionReplacedHof),
        shouldOptimizeConstants=shouldOptimizeConstants,
        hofFile=_escape_filename(equation_file),
        npopulations=int(populations),
        optimizer_algorithm=optimizer_algorithm,
        optimizer_nrestarts=int(optimizer_nrestarts),
        optimize_probability=float(optimize_probability),
        optimizer_iterations=int(optimizer_iterations),
        perturbationFactor=float(perturbationFactor),
        annealing=annealing,
        batching=batching,
        batchSize=int(min([batchSize, len(X)]) if batching else len(X)),
        mutationWeights=mutationWeights,
        warmupMaxsizeBy=float(warmupMaxsizeBy),
        useFrequency=useFrequency,
        npop=int(npop),
        ns=int(tournament_selection_n),
        probPickFirst=float(tournament_selection_p),
        ncyclesperiteration=int(ncyclesperiteration),
        fractionReplaced=float(fractionReplaced),
        topn=int(topn),
        verbosity=int(verbosity),
        progress=progress,
        terminal_width=int(term_width),
        **kwargs,
    )

    np_dtype = {16: np.float16, 32: np.float32, 64: np.float64}[precision]

    Main.X = np.array(X, dtype=np_dtype).T
    if len(y.shape) == 1:
        Main.y = np.array(y, dtype=np_dtype)
    else:
        Main.y = np.array(y, dtype=np_dtype).T
    if weights is not None:
        if len(weights.shape) == 1:
            Main.weights = np.array(weights, dtype=np_dtype)
        else:
            Main.weights = np.array(weights, dtype=np_dtype).T
    else:
        Main.weights = None

    cprocs = 0 if multithreading else procs

    raw_julia_output = Main.EquationSearch(
        Main.X,
        Main.y,
        weights=Main.weights,
        niterations=int(niterations),
        varMap=(variable_names if selection is None else
                [variable_names[i] for i in selection]),
        options=options,
        numprocs=int(cprocs),
        multithreading=bool(multithreading),
    )

    _set_globals(
        X=X,
        equation_file=equation_file,
        variable_names=variable_names,
        extra_sympy_mappings=extra_sympy_mappings,
        extra_torch_mappings=extra_torch_mappings,
        extra_jax_mappings=extra_jax_mappings,
        output_jax_format=output_jax_format,
        output_torch_format=output_torch_format,
        multioutput=multioutput,
        nout=nout,
        selection=selection,
        raw_julia_output=raw_julia_output,
    )

    equations = get_hof(
        equation_file=equation_file,
        n_features=X.shape[1],
        variable_names=variable_names,
        output_jax_format=output_jax_format,
        output_torch_format=output_torch_format,
        selection=selection,
        extra_sympy_mappings=extra_sympy_mappings,
        extra_jax_mappings=extra_jax_mappings,
        extra_torch_mappings=extra_torch_mappings,
        multioutput=multioutput,
        nout=nout,
    )

    if delete_tempfiles:
        shutil.rmtree(tmpdir)

    already_ran = True

    return equations