def extract_deformation_curve_solver(folder, x):
    # get simulation parameters
    with open(folder + '/parameters.txt', 'r') as f:
        lines = f.readlines()

    parameters = {}
    for line in lines:
        try:
            key, value = line.split('= ')
            value = value.split(' ')[0]
            parameters[key.strip()] = float(value.strip())
        except:
            pass

    # load coordinates
    M = saenopy.load(folder + "/solver.npz")
    coords = M.R
    # load displacements
    displ = M.U

    # compute binned normed displacements and normed coordinates
    u = np.sqrt(np.sum(coords**2.,
                       axis=1)) / (parameters['INNER_RADIUS'] * 10**-6)
    v = np.sqrt(np.sum(displ**2.,
                       axis=1)) / (parameters['INNER_RADIUS'] * 10**-6)

    y = np.array([
        np.nanmedian(v[(u >= x[i]) & (u < x[i + 1])])
        for i in range(len(x) - 1)
    ])

    # save results
    results = {
        'pressure': parameters['PRESSURE'],
        'bins': x,
        'displacements': y
    }
    return results
def distribute_solver(func,
                      const_args,
                      var_arg='pressure',
                      start=0.1,
                      end=1000,
                      n=120,
                      log_scaling=True,
                      n_cores=None,
                      get_initial=True,
                      max_iter=600,
                      step=0.0033,
                      conv_crit=0.01,
                      callback=None):
    # get_intial = True takes the deformationfield from previous simulation as start values for the next simulations, which reduces computation time

    # by default use spherical contraction as function
    func = spherical_contraction_solver

    if n_cores is None:
        n_cores = os.cpu_count()

    if log_scaling:
        values = np.logspace(np.log10(start), np.log10(end), n, endpoint=True)
    else:
        values = np.linspace(np.log10(start), np.log10(end), n, endpoint=True)

    outfolder = const_args['outfolder']
    del const_args['outfolder']

    if not os.path.exists(outfolder):
        os.makedirs(outfolder)

    np.savetxt(outfolder + '/' + var_arg + '-values.txt', values)

    values = list(values)

    if n_cores == 1:
        U = None
        for index, v in enumerate(values):
            func(const_args["meshfile"],
                 outfolder + '/simulation' + str(index).zfill(6), v,
                 const_args["material"], None, None, False, U, max_iter, step,
                 conv_crit)
            # get the last displacement
            name = outfolder + '/simulation' + str(index).zfill(
                6) + "/solver.npz"
            if os.path.exists(name):
                M = saenopy.load(name)
                U = M.U
            # optionally call the callback
            if callback is not None:
                callback(index, len(values))
    else:
        index = 0
        processes = []
        import multiprocessing
        while True:
            processes = [p for p in processes if p.is_alive()]

            if len(processes) < n_cores and index < len(values):
                v = values[index]
                U = None
                if get_initial == True:
                    for i in range(index - 3, -1, -1):
                        name = outfolder + '/simulation' + str(i).zfill(
                            6) + "/solver.npz"
                        if os.path.exists(name):
                            M = saenopy.load(name)
                            U = M.U
                            break
                p = multiprocessing.Process(
                    target=func,
                    args=(const_args["meshfile"],
                          outfolder + '/simulation' + str(index).zfill(6), v,
                          const_args["material"], None, None, False, U,
                          max_iter, step, conv_crit))

                p.start()
                processes.append(p)
                if callback is not None:
                    callback(index, len(values))
                index += 1
            sleep(1.)

            if len(processes) == 0:
                break
    if callback is not None:
        callback(index, len(values))
    return