def receiver(): """Receiver for messages from dm. """ instance = Instance({Operator.F_INIT: ['in']}) while instance.reuse_instance(): # f_init msg = instance.receive('in') assert msg.data == 'testing'
def reaction() -> None: """A simple exponential reaction model on a 1D grid. """ instance = Instance({ Operator.F_INIT: ['initial_state'], # list of float Operator.O_F: ['final_state']}) # list of float while instance.reuse_instance(): # F_INIT t_max = instance.get_setting('t_max', 'float') dt = instance.get_setting('dt', 'float') k = instance.get_setting('k', 'float') msg = instance.receive('initial_state') U = np.array(msg.data) t_cur = msg.timestamp while t_cur + dt < t_max: # O_I # S U += k * U * dt t_cur += dt # O_F instance.send('final_state', Message(t_cur, None, U.tolist()))
def macro(): instance = Instance({ Operator.O_I: ['out'], Operator.S: ['in']}) while instance.reuse_instance(): # f_init assert instance.get_setting('test1') == 13 for i in range(2): # o_i test_array = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) assert test_array.shape == (2, 3) assert test_array.flags.c_contiguous data = { 'message': 'testing', 'test_grid': test_array} instance.send('out', Message(i * 10.0, (i + 1) * 10.0, data)) # s/b msg = instance.receive('in') assert msg.data['reply'] == 'testing back {}'.format(i) assert msg.data['test_grid'].array.dtype.kind == 'i' assert msg.data['test_grid'].array.dtype.itemsize == 8 assert msg.data['test_grid'].array[0][1] == 2 assert msg.timestamp == i * 10.0
def micro(): """Micro model implementation. """ instance = Instance({Operator.F_INIT: ['in'], Operator.O_F: ['out']}) while instance.reuse_instance(): # f_init assert instance.get_setting('test3', 'str') == 'testing' assert instance.get_setting('test4', 'bool') is True assert instance.get_setting('test6', '[[float]]')[0][1] == 2.0 msg = instance.receive('in') assert msg.data == 'testing' # o_f instance.send('out', Message(0.1, None, 'testing back'))
def qmc(): """qMC implementation. """ instance = Instance({Operator.O_F: ['settings_out[]']}) while instance.reuse_instance(): # o_f settings0 = Settings({'test2': 14.4}) assert instance.is_connected('settings_out') assert instance.is_vector_port('settings_out') assert not instance.is_resizable('settings_out') length = instance.get_port_length('settings_out') assert length == 10 for slot in range(length): instance.send('settings_out', Message(0.0, None, settings0), slot)
def micro(): """Micro model implementation. """ instance = Instance({Operator.F_INIT: ['in'], Operator.O_F: ['out']}) assert instance.get_setting('test2') == 13.3 while instance.reuse_instance(): # f_init assert instance.get_setting('test2', 'float') == 14.4 msg = instance.receive('in') assert msg.data == 'testing' # with pytest.raises(RuntimeError): # instance.receive_with_settings('in') # o_f instance.send('out', Message(0.1, None, 'testing back'))
def macro(): """Macro model implementation. """ instance = Instance({ Operator.O_I: ['out'], Operator.S: ['in']}) while instance.reuse_instance(): # f_init assert instance.get_setting('test2') == 14.4 # o_i instance.send('out', Message(0.0, 10.0, 'testing')) # s/b msg = instance.receive('in') assert msg.data == 'testing back'
def macro(): """Macro model implementation. """ instance = Instance({Operator.O_I: ['out[]'], Operator.S: ['in[]']}) while instance.reuse_instance(): # f_init assert instance.get_setting('test1') == 13 # o_i assert instance.is_vector_port('out') for slot in range(10): instance.send('out', Message(0.0, 10.0, 'testing'), slot) # s/b for slot in range(10): msg = instance.receive('in', slot) assert msg.data == 'testing back'
def micro(): """Micro model implementation. """ instance = Instance({Operator.F_INIT: ['in'], Operator.O_F: ['out']}) while instance.reuse_instance(): # f_init assert instance.get_setting('test3', 'str') == 'testing' assert instance.get_setting('test4', 'bool') is True assert instance.get_setting('test6', '[[float]]')[0][1] == 2.0 msg = instance.receive('in') assert msg.data == 'testing' # o_f result = { 'string': 'testing back', 'int': 42, 'float': 3.1416, 'grid': Grid(np.array([[12.0, 34.0, 56.0], [1.0, 2.0, 3.0]])) } instance.send('out', Message(0.1, None, result))
def macro(): instance = Instance({Operator.O_I: ['out'], Operator.S: ['in']}) while instance.reuse_instance(): # f_init assert instance.get_setting('test1') == 13 for i in range(2): # o_i instance.send('out', Message(i * 10.0, (i + 1) * 10.0, 'testing')) # s/b msg = instance.receive('in') assert msg.data == 'testing back {}'.format(i) assert msg.timestamp == i * 10.0
def duplication_mapper(): """Duplication mapper implementation. """ instance = Instance() while instance.reuse_instance(): # o_f out_ports = instance.list_ports()[Operator.O_F] message = Message(0.0, None, 'testing') for out_port in out_ports: instance.send(out_port, message)
def macro(): """Macro model implementation. """ instance = Instance({Operator.O_I: ['out[]'], Operator.S: ['in[]']}) while instance.reuse_instance(): # f_init assert instance.get_setting('test1') == 13 # o_i assert instance.is_vector_port('out') for slot in range(10): instance.send('out', Message(0.0, 10.0, 'testing'), slot) # s/b for slot in range(10): msg = instance.receive('in', slot) assert msg.data['string'] == 'testing back' assert msg.data['int'] == 42 assert msg.data['float'] == 3.1416 assert msg.data['grid'].array.dtype == np.float64 assert msg.data['grid'].array[0, 1] == 34.0
def diffusion() -> None: """A simple diffusion model on a 1d grid. The state of this model is a 1D grid of concentrations. It sends out the state on each timestep on `state_out`, and can receive an updated state on `state_in` at each state update. """ logger = logging.getLogger() instance = Instance({ Operator.O_I: ['state_out'], Operator.S: ['state_in'] }) while instance.reuse_instance(): # F_INIT t_max = instance.get_setting('t_max', 'float') dt = instance.get_setting('dt', 'float') x_max = instance.get_setting('x_max', 'float') dx = instance.get_setting('dx', 'float') d = instance.get_setting('d', 'float') U = np.zeros(int(round(x_max / dx))) U[25] = 2.0 U[50] = 2.0 U[75] = 2.0 Us = U t_cur = 0.0 while t_cur + dt <= t_max: # O_I t_next = t_cur + dt if t_next + dt > t_max: t_next = None cur_state_msg = Message(t_cur, t_next, U.tolist()) instance.send('state_out', cur_state_msg) # S msg = instance.receive('state_in', default=cur_state_msg) if msg.timestamp > t_cur + dt: logger.warning('Received a message from the future!') U = np.array(msg.data) dU = np.zeros_like(U) dU[1:-1] = d * laplacian(U, dx) * dt dU[0] = dU[1] dU[-1] = dU[-2] U += dU Us = np.vstack((Us, U)) t_cur += dt plt.figure() plt.imshow(np.log(Us + 1e-20)) plt.show()
def reduced_sgs(): """ An EasySurrogate Reduced micro model, executed in a separate file and linked to the macro model via MUSCLE3 """ instance = Instance({ Operator.F_INIT: ['state_in'], # a dict with state values Operator.O_F: ['sgs_out'] }) # a dict with subgrid-scale terms # get some parameters N_Q = instance.get_setting('N_Q') # the number of QoI to track, per PDE N_LF = instance.get_setting( 'N_LF') # the number of gridpoints in 1 dimension # t_max = instance.get_setting('t_max') #the simulation time, per time-step of macro # dt = instance.get_setting('dt') #the micro time step logger = logging.getLogger() logger.setLevel(logging.DEBUG) # Create an EasySurrogate RecucedSurrogate object surrogate = es.methods.Reduced_Surrogate(N_Q, N_LF) while instance.reuse_instance(): # receive the state from the macro model msg = instance.receive('state_in') V_hat_1_re = msg.data['V_hat_1_re'].array.T V_hat_1_im = msg.data['V_hat_1_im'].array.T u_hat_re = msg.data['u_hat_re'].array.T u_hat_im = msg.data['u_hat_im'].array.T v_hat_re = msg.data['v_hat_re'].array.T v_hat_im = msg.data['v_hat_im'].array.T Q_ref = msg.data['Q_ref'].array.T Q_model = msg.data['Q_model'].array.T # recreate the Fourier coefficients (temporary fix) V_hat_1 = V_hat_1_re + 1.0j * V_hat_1_im u_hat = u_hat_re + 1.0j * u_hat_im v_hat = v_hat_re + 1.0j * v_hat_im # #time of the macro model t_cur = msg.timestamp # train the two reduced sgs source terms using the recieved reference data Q_ref reduced_dict_u = surrogate.train([V_hat_1, u_hat], Q_ref[0:N_Q] - Q_model[0:N_Q]) reduced_dict_v = surrogate.train([V_hat_1, v_hat], Q_ref[N_Q:] - Q_model[N_Q:]) # get the two reduced sgs terms from the dict reduced_sgs_u = np.fft.ifft2(reduced_dict_u['sgs_hat']) reduced_sgs_v = np.fft.ifft2(reduced_dict_v['sgs_hat']) # MUSCLE O_F port (sgs), send the subgrid-scale terms back to the macro model reduced_sgs_u_re = np.copy(reduced_sgs_u.real) reduced_sgs_u_im = np.copy(reduced_sgs_u.imag) reduced_sgs_v_re = np.copy(reduced_sgs_v.real) reduced_sgs_v_im = np.copy(reduced_sgs_v.imag) instance.send( 'sgs_out', Message( t_cur, None, { 'reduced_sgs_u_re': reduced_sgs_u_re, 'reduced_sgs_u_im': reduced_sgs_u_im, 'reduced_sgs_v_re': reduced_sgs_v_re, 'reduced_sgs_v_im': reduced_sgs_v_im }))
def qmc_driver() -> None: """A driver for quasi-Monte Carlo Uncertainty Quantification. This component attaches to a collection of model instances, and feeds in different parameter values generated using a Sobol sequence. """ instance = Instance({ Operator.O_I: ['parameters_out[]'], Operator.S: ['states_in[]'] }) while instance.reuse_instance(): # F_INIT # get and check parameter distributions n_samples = instance.get_setting('n_samples', 'int') d_min = instance.get_setting('d_min', 'float') d_max = instance.get_setting('d_max', 'float') k_min = instance.get_setting('k_min', 'float') k_max = instance.get_setting('k_max', 'float') if d_max < d_min: instance.error_shutdown('Invalid settings: d_max < d_min') exit(1) if k_max < k_min: instance.error_shutdown('Invalid settings: k_max < k_min') exit(1) # generate UQ parameter values sobol_sqn = sobol_seq.i4_sobol_generate(2, n_samples) ds = d_min + sobol_sqn[:, 0] * (d_max - d_min) ks = k_min + sobol_sqn[:, 1] * (k_max - k_min) # configure output port if not instance.is_resizable('parameters_out'): instance.error_shutdown( 'This component needs a resizable' ' parameters_out port, but it is connected to' ' something that cannot be resized. Maybe try' ' adding a load balancer.') exit(1) instance.set_port_length('parameters_out', n_samples) # run ensemble Us = None # O_I for sample in range(n_samples): uq_parameters = Settings({'d': ds[sample], 'k': ks[sample]}) msg = Message(0.0, None, uq_parameters) instance.send('parameters_out', msg, sample) # S for sample in range(n_samples): msg = instance.receive_with_settings('states_in', sample) U = np.array(msg.data) # accumulate if Us is None: Us = U else: Us = np.vstack((Us, U)) mean = np.mean(Us, axis=0) plt.figure() plt.imshow(np.log(Us + 1e-20)) plt.show()
def explicit_relay(): """Intermediate component with explicit settings. Sends and receives overlay settings explicitly, rather than having MUSCLE handle them. This just passes all information on. """ instance = Instance({ Operator.F_INIT: ['in[]'], Operator.O_F: ['out[]']}) while instance.reuse_instance(False): # f_init assert instance.get_setting('test2', 'float') == 13.3 assert instance.get_port_length('in') == instance.get_port_length( 'out') msgs = list() for slot in range(instance.get_port_length('in')): msg = instance.receive_with_settings('in', slot) assert msg.data.startswith('testing') assert msg.settings['test2'] == 14.4 msgs.append(msg) assert instance.get_setting('test2') == 13.3 # o_f for slot in range(instance.get_port_length('out')): instance.send('out', msgs[slot], slot)
def gray_scott_macro(): ####################### # MUSCLE modification # ####################### # define the MUSCLE in and out ports instance = Instance({Operator.O_I: ['state_out'], Operator.S: ['sgs_in']}) while instance.reuse_instance(): # Main script # gray scott parameters feed = instance.get_setting('feed') kill = instance.get_setting('kill') HOME = os.path.abspath(os.path.dirname(__file__)) ########################### # End MUSCLE modification # ########################### # number of gridpoints in 1D I = 7 N = 2**I N_ref = 2**(I + 1) # number of time series to track N_Q = 2 # domain size [-L, L] L = 1.25 # user flags store = True state_store = True restart = False sim_ID = 'test_gray_scott' # TRAINING DATA SET QoI = ['Q_HF', 'Q_ref'] Q = len(QoI) # allocate memory samples = {} if store: samples['N'] = N for q in range(Q): samples[QoI[q]] = [] # 2D grid, scaled by L xx, yy = get_grid(N, L) xx_ref, yy_ref = get_grid(N_ref, L) # spatial derivative operators kx, ky = get_derivative_operator(N, L) kx_ref, ky_ref = get_derivative_operator(N_ref, L) # Laplace operator k_squared = kx**2 + ky**2 k_squared_ref = kx_ref**2 + ky_ref**2 # diffusion coefficients epsilon_u = 2e-5 epsilon_v = 1e-5 # time step parameters dt = 0.5 n_steps = int(5000 / dt) store_frame_rate = 1 t = 0.0 # Initial condition if restart: fname = HOME + '/restart/' + sim_ID + '_t_' + str(np.around( t, 1)) + '.hdf5' # if fname does not exist, select restart file via GUI if os.path.exists(fname) == False: root = tk.Tk() root.withdraw() fname = filedialog.askopenfilename( initialdir=HOME + '/restart', title="Open restart file", filetypes=(('HDF5 files', '*.hdf5'), ('All files', '*.*'))) # create HDF5 file h5f = h5py.File(fname, 'r') for key in h5f.keys(): print(key) vars()[key] = h5f[key][:] h5f.close() else: u_hat, v_hat = initial_cond(xx, yy) u_hat_ref, v_hat_ref = initial_cond(xx_ref, yy_ref) # Integrating factors int_fac_u, int_fac_u2, int_fac_v, int_fac_v2 = \ integrating_factors(k_squared, dt, epsilon_u, epsilon_v) int_fac_u_ref, int_fac_u2_ref, int_fac_v_ref, int_fac_v2_ref = \ integrating_factors(k_squared_ref, dt, epsilon_u, epsilon_v) # counters j = 0 j2 = 0 V_hat_1 = np.fft.fft2(np.ones([N, N])) V_hat_1_ref = np.fft.fft2(np.ones([N_ref, N_ref])) t0 = time.time() samples_uq = np.zeros([n_steps, 8]) # time stepping for n in range(n_steps): u_hat_ref, v_hat_ref = rk4(u_hat_ref, v_hat_ref, int_fac_u_ref, int_fac_u2_ref, int_fac_v_ref, int_fac_v2_ref, dt, feed, kill) # compute reference stats Q_HF = np.zeros(2 * N_Q) Q_HF[0] = compute_int(V_hat_1, u_hat, N) Q_HF[1] = 0.5 * compute_int(u_hat, u_hat, N) Q_HF[2] = compute_int(V_hat_1, v_hat, N) Q_HF[3] = 0.5 * compute_int(v_hat, v_hat, N) Q_ref = np.zeros(2 * N_Q) Q_ref[0] = compute_int(V_hat_1_ref, u_hat_ref, N_ref) Q_ref[1] = 0.5 * compute_int(u_hat_ref, u_hat_ref, N_ref) Q_ref[2] = compute_int(V_hat_1_ref, v_hat_ref, N_ref) Q_ref[3] = 0.5 * compute_int(v_hat_ref, v_hat_ref, N_ref) samples_uq[n, 0:2 * N_Q] = Q_HF samples_uq[n, 2 * N_Q:] = Q_ref if np.mod(n, 100) == 0: print('time step %d of %d' % (n, n_steps)) print(Q_HF) print(Q_ref) ####################### # MUSCLE modification # ####################### # MUSCLE O_I port (state_out) t_cur = n * dt t_next = t_cur + dt if n == n_steps - 1: t_next = None # split state vars in real and imag part (temporary fix) V_hat_1_re = np.copy(V_hat_1.real) V_hat_1_im = np.copy(V_hat_1.imag) u_hat_re = np.copy(u_hat.real) u_hat_im = np.copy(u_hat.imag) v_hat_re = np.copy(v_hat.real) v_hat_im = np.copy(v_hat.imag) # create a MUSCLE Message object, to be sent to the micro model cur_state = Message( t_cur, t_next, { 'V_hat_1_re': V_hat_1_re, 'V_hat_1_im': V_hat_1_im, 'u_hat_re': u_hat_re, 'u_hat_im': u_hat_im, 'v_hat_re': v_hat_re, 'v_hat_im': v_hat_im, 'Q_ref': Q_ref, 'Q_model': Q_HF }) # send the message to the micro model instance.send('state_out', cur_state) # reveive a message from the micro model, i.e. the two reduced subgrid-scale terms msg = instance.receive('sgs_in') reduced_sgs_u_re = msg.data['reduced_sgs_u_re'].array reduced_sgs_u_im = msg.data['reduced_sgs_u_im'].array reduced_sgs_v_re = msg.data['reduced_sgs_v_re'].array reduced_sgs_v_im = msg.data['reduced_sgs_v_im'].array # recreate the reduced subgrid-scale terms for the u and v pde reduced_sgs_u = reduced_sgs_u_re + 1.0j * reduced_sgs_u_im reduced_sgs_v = reduced_sgs_v_re + 1.0j * reduced_sgs_v_im ########################### # End MUSCLE modification # ########################### if np.mod(n, 1000) == 0: print('time step %d of %d' % (n, n_steps)) # evolve the state in time, with the new reduced sgs terms u_hat, v_hat = rk4(u_hat, v_hat, int_fac_u, int_fac_u2, int_fac_v, int_fac_v2, dt, feed, kill, reduced_sgs_u=reduced_sgs_u, reduced_sgs_v=reduced_sgs_v) j += 1 j2 += 1 t += dt if j2 == store_frame_rate and store: j2 = 0 for qoi in QoI: samples[qoi].append(eval(qoi)) # foo = False t1 = time.time() print('*************************************') print('Simulation time = %f [s]' % (t1 - t0)) print('*************************************') # output csv file header = 'Q1,Q2,Q3,Q4,Q1_HF,Q2_HF,Q3_HF,Q4_HF' fname = 'output_f%.5f_k%.5f.csv' % (feed, kill) np.savetxt(fname, samples_uq, delimiter=",", comments='', header=header) # store the state of the system to allow for a simulation restart at t > 0 if state_store: keys = ['u_hat', 'v_hat'] if os.path.exists(HOME + '/restart') == False: os.makedirs(HOME + '/restart') fname = HOME + '/restart/' + sim_ID + '_t_' + str(np.around( t, 1)) + '.hdf5' # create HDF5 file h5f = h5py.File(fname, 'w') # store numpy sample arrays as individual datasets in the hdf5 file for key in keys: qoi = eval(key) h5f.create_dataset(key, data=qoi) h5f.close()
def diffusion() -> None: """A simple diffusion model on a 1d grid. The state of this model is a 1D grid of concentrations. It sends out the state on each timestep on `state_out`, and can receive an updated state on `state_in` at each state update. """ logger = logging.getLogger() instance = Instance({ Operator.O_I: ['state_out'], Operator.S: ['state_in'] }) while instance.reuse_instance(): # F_INIT t_max = instance.get_setting('t_max', 'float') dt = instance.get_setting('dt', 'float') x_max = instance.get_setting('x_max', 'float') dx = instance.get_setting('dx', 'float') d = instance.get_setting('d', 'float') U = np.zeros(int(round(x_max / dx))) + 1e-20 U[25] = 2.0 U[50] = 2.0 U[75] = 2.0 Us = U t_cur = 0.0 while t_cur + dt <= t_max: # O_I t_next = t_cur + dt if t_next + dt > t_max: t_next = None cur_state_msg = Message(t_cur, t_next, Grid(U, ['x'])) instance.send('state_out', cur_state_msg) # S msg = instance.receive('state_in', default=cur_state_msg) if msg.timestamp > t_cur + dt: logger.warning('Received a message from the future!') np.copyto(U, msg.data.array) dU = np.zeros_like(U) dU[1:-1] = d * laplacian(U, dx) * dt dU[0] = dU[1] dU[-1] = dU[-2] U += dU Us = np.vstack((Us, U)) t_cur += dt if 'DONTPLOT' not in os.environ: from matplotlib import pyplot as plt plt.figure() plt.imshow(np.log(Us + 1e-20), origin='upper', extent=[ -0.5 * dx, x_max - 0.5 * dx, (t_max - 0.5 * dt) * 1000.0, -0.5 * dt * 1000.0 ], interpolation='none', aspect='auto') cbar = plt.colorbar() cbar.set_label('log(Concentration)', rotation=270, labelpad=20) plt.xlabel('x') plt.ylabel('t (ms)') plt.title('Concentration over time') plt.show()
def qmc_driver() -> None: """A driver for quasi-Monte Carlo Uncertainty Quantification. This component attaches to a collection of model instances, and feeds in different parameter values generated using a Sobol sequence. """ instance = Instance({ Operator.O_I: ['parameters_out[]'], Operator.S: ['states_in[]']}) while instance.reuse_instance(): # F_INIT # get and check parameter distributions n_samples = instance.get_setting('n_samples', 'int') d_min = instance.get_setting('d_min', 'float') d_max = instance.get_setting('d_max', 'float') k_min = instance.get_setting('k_min', 'float') k_max = instance.get_setting('k_max', 'float') if d_max < d_min: instance.error_shutdown('Invalid settings: d_max < d_min') exit(1) if k_max < k_min: instance.error_shutdown('Invalid settings: k_max < k_min') exit(1) # generate UQ parameter values sobol_sqn = sobol_seq.i4_sobol_generate(2, n_samples) ds = d_min + sobol_sqn[:, 0] * (d_max - d_min) ks = k_min + sobol_sqn[:, 1] * (k_max - k_min) # configure output port if not instance.is_resizable('parameters_out'): instance.error_shutdown('This component needs a resizable' ' parameters_out port, but it is connected to' ' something that cannot be resized. Maybe try' ' adding a load balancer.') exit(1) instance.set_port_length('parameters_out', n_samples) # run ensemble Us = None # O_I for sample in range(n_samples): uq_parameters = Settings({ 'd': ds[sample], 'k': ks[sample]}) msg = Message(0.0, None, uq_parameters) instance.send('parameters_out', msg, sample) # S for sample in range(n_samples): msg = instance.receive_with_settings('states_in', sample) U = np.array(msg.data) # accumulate if Us is None: Us = U else: Us = np.vstack((Us, U)) mean = np.mean(Us, axis=0) # O_F if 'DONTPLOT' not in os.environ: from matplotlib import pyplot as plt t_max = instance.get_setting('t_max', 'float') dt = instance.get_setting('dt', 'float') x_max = instance.get_setting('x_max', 'float') dx = instance.get_setting('dx', 'float') plt.figure() plt.imshow( np.log(Us + 1e-20), origin='upper', extent=[ -0.5*dx, x_max - 0.5*dx, n_samples-0.5, -0.5], interpolation='none', aspect='auto' ) cbar = plt.colorbar() cbar.set_label('log(Concentration)', rotation=270, labelpad=20) plt.xlabel('x') plt.ylabel('Sample') plt.title('Final states') plt.show()
def load_balancer() -> None: """A proxy which divides many calls over few instances. Put this component between a driver and a set of models, or between a macro model and a set of micro models. It will let the driver or macro-model submit as many calls as it wants, and divide them over the available (micro)model instances in a round-robin fashion. Assumes a fixed number of micro-model instances. Ports: front_in: Input for calls, connect to driver/macro-model O_I. back_out: Output to workers, connect to F_INIT of instances that do the work. back_in: Input for results, connect to O_F of instances that do the work. front_out: Output back to driver/macro-model S. """ instance = Instance({ Operator.F_INIT: ['front_in[]'], Operator.O_I: ['back_out[]'], Operator.S: ['back_in[]'], Operator.O_F: ['front_out[]']}) while instance.reuse_instance(False): # F_INIT started = 0 # number started and index of next to start done = 0 # number done and index of next to return num_calls = instance.get_port_length('front_in') num_workers = instance.get_port_length('back_out') instance.set_port_length('front_out', num_calls) while done < num_calls: while started - done < num_workers and started < num_calls: msg = instance.receive_with_settings('front_in', started) instance.send('back_out', msg, started % num_workers) started += 1 msg = instance.receive_with_settings('back_in', done % num_workers) instance.send('front_out', msg, done) done += 1