Example #1
0
    def __init__(self,
                 circuit: MultiCircuit,
                 options: PowerFlowOptions,
                 max_iter=1000):
        """
        Constructor
        Args:
            circuit: Grid to cascade
            options: Power flow Options
            max_iter: max iterations
        """

        QThread.__init__(self)

        self.circuit = circuit

        self.options = options

        self.__cancel__ = False

        # initialize the power flow
        self.power_flow = PowerFlow(self.circuit, self.options)

        self.max_eval = max_iter
        n = len(self.circuit.buses)
        m = len(self.circuit.branches)

        # the dimension is the number of nodes
        self.dim = n

        # results
        self.results = MonteCarloResults(n, m, self.max_eval)

        # variables for the optimization
        self.xlow = zeros(n)  # lower bounds
        self.xup = ones(n)
        self.info = ""  # info
        self.integer = array([])  # integer variables
        self.continuous = arange(0, n, 1)  # continuous variables
        self.solution = None
        self.optimization_values = None
        self.it = 0

        # compile
        # compile circuits
        self.numerical_circuit = self.circuit.compile()
        self.numerical_input_islands = self.numerical_circuit.compute()
Example #2
0
    def perform_step_run(self):
        """
        Perform only one step cascading
        Returns:
            Nothing
        """

        # recompile the grid
        self.grid.compile()

        # initialize the simulator
        if self.cascade_type is CascadeType.PowerFlow:
            model_simulator = PowerFlow(self.grid, self.options)

        elif self.cascade_type is CascadeType.LatinHypercube:
            model_simulator = LatinHypercubeSampling(
                self.grid, self.options, sampling_points=self.n_lhs_samples)

        else:
            model_simulator = PowerFlow(self.grid, self.options)

        # For every circuit, run a power flow
        # for c in self.grid.circuits:
        model_simulator.run()

        if self.current_step == 0:
            # the first iteration try to trigger the selected indices, if any
            idx = self.remove_elements(self.grid, idx=self.triggering_idx)
        else:
            # cascade normally
            idx = self.remove_elements(self.grid)

        # store the removed indices and the results
        entry = CascadingReportElement(idx, model_simulator.results)
        self.results.events.append(entry)

        # increase the step number
        self.current_step += 1

        # print(model_simulator.results.get_convergence_report())

        # send the finnish signal
        self.progress_signal.emit(0.0)
        self.progress_text.emit('Done!')
        self.done_signal.emit()
def test_xfo_static_tap_3():
    """
    Basic test with the main transformer's  HV tap (X_C3) set at -2.5%
    (0.975 pu), which raises the LV by the same amount (+2.5%).
    """
    grid = MultiCircuit(name=test_name)
    grid.Sbase = Sbase
    grid.time_profile = None
    grid.logger = list()

    # Create buses
    POI = Bus(
        name="POI",
        vnom=100,  # kV
        is_slack=True)
    grid.add_bus(POI)

    B_C3 = Bus(name="B_C3", vnom=10)  # kV
    grid.add_bus(B_C3)

    B_MV_M32 = Bus(name="B_MV_M32", vnom=10)  # kV
    grid.add_bus(B_MV_M32)

    B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6)  # kV
    grid.add_bus(B_LV_M32)

    # Create voltage controlled generators (or slack, a.k.a. swing)
    UT = ControlledGenerator(name="Utility")
    UT.bus = POI
    grid.add_controlled_generator(POI, UT)

    # Create static generators (with fixed power factor)
    M32 = StaticGenerator(name="M32", power=4.2 + 0.0j)  # MVA (complex)
    M32.bus = B_LV_M32
    grid.add_static_generator(B_LV_M32, M32)

    # Create transformer types
    s = 5  # MVA
    z = 8  # %
    xr = 40
    SS = TransformerType(
        name="SS",
        hv_nominal_voltage=100,  # kV
        lv_nominal_voltage=10,  # kV
        nominal_power=s,
        copper_losses=complexe(z, xr).real * s * 1000 / Sbase,
        iron_losses=6.25,  # kW
        no_load_current=0.5,  # %
        short_circuit_voltage=z)
    grid.add_transformer_type(SS)

    s = 5  # MVA
    z = 6  # %
    xr = 20
    PM = TransformerType(
        name="PM",
        hv_nominal_voltage=10,  # kV
        lv_nominal_voltage=0.6,  # kV
        nominal_power=s,
        copper_losses=complexe(z, xr).real * s * 1000 / Sbase,
        iron_losses=6.25,  # kW
        no_load_current=0.5,  # %
        short_circuit_voltage=z)
    grid.add_transformer_type(PM)

    # Create branches
    X_C3 = Branch(bus_from=POI,
                  bus_to=B_C3,
                  name="X_C3",
                  branch_type=BranchType.Transformer,
                  template=SS,
                  tap=0.975)
    # update to a more precise tap changer
    X_C3.apply_tap_changer(
        TapChanger(taps_up=20, taps_down=20, max_reg=1.1, min_reg=0.9))
    grid.add_branch(X_C3)

    C_M32 = Branch(bus_from=B_C3,
                   bus_to=B_MV_M32,
                   name="C_M32",
                   r=0.784,
                   x=0.174)
    grid.add_branch(C_M32)

    X_M32 = Branch(bus_from=B_MV_M32,
                   bus_to=B_LV_M32,
                   name="X_M32",
                   branch_type=BranchType.Transformer,
                   template=PM)
    grid.add_branch(X_M32)

    # Apply templates (device types)
    grid.apply_all_branch_types()

    print("Buses:")
    for i, b in enumerate(grid.buses):
        print(f" - bus[{i}]: {b}")
    print()

    grid.compile()
    options = PowerFlowOptions(SolverType.NR,
                               verbose=True,
                               robust=True,
                               initialize_with_existing_solution=True,
                               multi_core=True,
                               control_q=True,
                               control_taps=True,
                               tolerance=1e-6,
                               max_iter=15)

    power_flow = PowerFlow(grid, options)
    power_flow.run()

    print()
    print(f"Test: {test_name}")
    print()

    print("Controlled generators:")
    for g in grid.get_controlled_generators():
        print(f" - Generator {g}: q_min={g.Qmin} MVAR, q_max={g.Qmax} MVAR")
    print()

    print("Branches:")
    for b in grid.branches:
        print(f" - {b}:")
        print(f"   R = {round(b.R, 4)} pu")
        print(f"   X = {round(b.X, 4)} pu")
        print(f"   X/R = {round(b.X/b.R, 1)}")
        print(f"   G = {round(b.G, 4)} pu")
        print(f"   B = {round(b.B, 4)} pu")
    print()

    print("Transformer types:")
    for t in grid.transformer_types:
        print(
            f" - {t}: Copper losses={int(t.Copper_losses)}kW, "
            f"Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%"
        )
    print()

    print("Losses:")
    for i in range(len(grid.branches)):
        print(
            f" - {grid.branches[i]}: losses={1000*round(power_flow.results.losses[i], 3)} kVA"
        )
    print()

    equal = False
    for i, branch in enumerate(grid.branches):
        if branch.name == "X_C3":
            equal = power_flow.results.tap_module[i] == branch.tap_module

    if not equal:
        grid.export_pf(f"{test_name}_results.xlsx", power_flow.results)
        grid.save_excel(f"{test_name}_grid.xlsx")

    assert equal
Example #4
0
def test_pv_1():
    """
    Voltage controlled generator test, also useful for a basic tutorial. In this
    case the generator M32 regulates the voltage at a setpoint of 1.025 pu, and
    the slack bus (POI) regulates it at 1.0 pu.

    The transformers' magnetizing branch losses are considered, but their
    voltage regulators aren't.
    """
    grid = MultiCircuit(name=test_name)
    grid.Sbase = Sbase
    grid.time_profile = None
    grid.logger = list()

    # Create buses
    POI = Bus(name="POI",
              vnom=100, #kV
              is_slack=True)
    grid.add_bus(POI)

    B_MV_M32 = Bus(name="B_MV_M32",
                   vnom=10) #kV
    grid.add_bus(B_MV_M32)

    B_LV_M32 = Bus(name="B_LV_M32",
                   vnom=0.6) #kV
    grid.add_bus(B_LV_M32)

    # Create voltage controlled generators (or slack, a.k.a. swing)
    UT = ControlledGenerator(name="Utility")
    UT.bus = POI
    grid.add_controlled_generator(POI, UT)

    M32 = ControlledGenerator(name="M32",
                              active_power=4.2,
                              voltage_module=1.025,
                              Qmin=-2.5,
                              Qmax=2.5)
    M32.bus = B_LV_M32
    grid.add_controlled_generator(B_LV_M32, M32)

    # Create transformer types
    s = 100 # MVA
    z = 8 # %
    xr = 40
    SS = TransformerType(name="SS",
                         hv_nominal_voltage=100, # kV
                         lv_nominal_voltage=10, # kV
                         nominal_power=s,
                         copper_losses=complexe(z, xr).real*s*1000/Sbase,
                         iron_losses=125, # kW
                         no_load_current=0.5, # %
                         short_circuit_voltage=z)
    grid.add_transformer_type(SS)

    s = 5 # MVA
    z = 6 # %
    xr = 20
    PM = TransformerType(name="PM",
                         hv_nominal_voltage=10, # kV
                         lv_nominal_voltage=0.6, # kV
                         nominal_power=s,
                         copper_losses=complexe(z, xr).real*s*1000/Sbase,
                         iron_losses=6.25, # kW
                         no_load_current=0.5, # %
                         short_circuit_voltage=z)
    grid.add_transformer_type(PM)

    # Create branches
    X_C3 = Branch(bus_from=POI,
                  bus_to=B_MV_M32,
                  name="X_C3",
                  branch_type=BranchType.Transformer,
                  template=SS)
    grid.add_branch(X_C3)

    X_M32 = Branch(bus_from=B_MV_M32,
                   bus_to=B_LV_M32,
                   name="X_M32",
                   branch_type=BranchType.Transformer,
                   template=PM)
    grid.add_branch(X_M32)

    # Apply templates (device types)
    grid.apply_all_branch_types()

    print("Buses:")
    for i, b in enumerate(grid.buses):
        print(f" - bus[{i}]: {b}")
    print()

    grid.compile()
    options = PowerFlowOptions(SolverType.LM,
                               verbose=True,
                               robust=True,
                               initialize_with_existing_solution=True,
                               multi_core=True,
                               control_q=True,
                               tolerance=1e-6,
                               max_iter=99)

    power_flow = PowerFlow(grid, options)
    power_flow.run()

    approx_volt = [round(100*abs(v), 1) for v in power_flow.results.voltage]
    solution = [100.0, 100.1, 102.5] # Expected solution from GridCal and ETAP 16.1.0, for reference

    print()
    print(f"Test: {test_name}")
    print(f"Results:  {approx_volt}")
    print(f"Solution: {solution}")
    print()

    print("Controlled generators:")
    for g in grid.get_controlled_generators():
        print(f" - Generator {g}: q_min={g.Qmin}pu, q_max={g.Qmax}pu")
    print()

    print("Branches:")
    for b in grid.branches:
        print(f" - {b}:")
        print(f"   R = {round(b.R, 4)} pu")
        print(f"   X = {round(b.X, 4)} pu")
        print(f"   X/R = {round(b.X/b.R, 1)}")
        print(f"   G = {round(b.G, 4)} pu")
        print(f"   B = {round(b.B, 4)} pu")
    print()

    print("Transformer types:")
    for t in grid.transformer_types:
        print(f" - {t}: Copper losses={int(t.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%")
    print()

    print("Losses:")
    for i in range(len(grid.branches)):
        print(f" - {grid.branches[i]}: losses={1000*round(power_flow.results.losses[i], 3)} kVA")
    print()

    equal = True
    for i in range(len(approx_volt)):
        if approx_volt[i] != solution[i]:
            equal = False

    assert equal
Example #5
0
def test_xfo_static_tap_1():
    """
    Basic test with the main transformer's  HV tap (X_C3) set at +5% (1.05 pu),
    which lowers the LV by the same amount (-5%).
    """
    grid = MultiCircuit(name=test_name)
    grid.Sbase = Sbase
    grid.time_profile = None
    grid.logger = list()

    # Create buses
    POI = Bus(
        name="POI",
        vnom=100,  #kV
        is_slack=True)
    grid.add_bus(POI)

    B_C3 = Bus(name="B_C3", vnom=10)  #kV
    grid.add_bus(B_C3)

    B_MV_M32 = Bus(name="B_MV_M32", vnom=10)  #kV
    grid.add_bus(B_MV_M32)

    B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6)  #kV
    grid.add_bus(B_LV_M32)

    # Create voltage controlled generators (or slack, a.k.a. swing)
    UT = ControlledGenerator(name="Utility")
    UT.bus = POI
    grid.add_controlled_generator(POI, UT)

    # Create static generators (with fixed power factor)
    M32 = StaticGenerator(name="M32", power=4.2 + 0.0j)  # MVA (complex)
    M32.bus = B_LV_M32
    grid.add_static_generator(B_LV_M32, M32)

    # Create transformer types
    s = 5  # MVA
    z = 8  # %
    xr = 40
    SS = TransformerType(
        name="SS",
        hv_nominal_voltage=100,  # kV
        lv_nominal_voltage=10,  # kV
        nominal_power=s,
        copper_losses=complexe(z, xr).real * s * 1000 / Sbase,
        iron_losses=6.25,  # kW
        no_load_current=0.5,  # %
        short_circuit_voltage=z)
    grid.add_transformer_type(SS)

    s = 5  # MVA
    z = 6  # %
    xr = 20
    PM = TransformerType(
        name="PM",
        hv_nominal_voltage=10,  # kV
        lv_nominal_voltage=0.6,  # kV
        nominal_power=s,
        copper_losses=complexe(z, xr).real * s * 1000 / Sbase,
        iron_losses=6.25,  # kW
        no_load_current=0.5,  # %
        short_circuit_voltage=z)
    grid.add_transformer_type(PM)

    # Create branches
    X_C3 = Branch(bus_from=POI,
                  bus_to=B_C3,
                  name="X_C3",
                  branch_type=BranchType.Transformer,
                  template=SS,
                  tap=1.05)
    grid.add_branch(X_C3)

    C_M32 = Branch(bus_from=B_C3,
                   bus_to=B_MV_M32,
                   name="C_M32",
                   r=0.784,
                   x=0.174)
    grid.add_branch(C_M32)

    X_M32 = Branch(bus_from=B_MV_M32,
                   bus_to=B_LV_M32,
                   name="X_M32",
                   branch_type=BranchType.Transformer,
                   template=PM)
    grid.add_branch(X_M32)

    # Apply templates (device types)
    grid.apply_all_branch_types()

    print("Buses:")
    for i, b in enumerate(grid.buses):
        print(f" - bus[{i}]: {b}")
    print()

    grid.compile()
    options = PowerFlowOptions(SolverType.LM,
                               verbose=True,
                               robust=True,
                               initialize_with_existing_solution=True,
                               multi_core=True,
                               control_q=True,
                               tolerance=1e-6,
                               max_iter=99)

    power_flow = PowerFlow(grid, options)
    power_flow.run()

    approx_volt = [round(100 * abs(v), 1) for v in power_flow.results.voltage]
    solution = [100.0, 94.7, 98.0, 98.1]  # Expected solution from GridCal

    print()
    print(f"Test: {test_name}")
    print(f"Results:  {approx_volt}")
    print(f"Solution: {solution}")
    print()

    print("Controlled generators:")
    for g in grid.get_controlled_generators():
        print(f" - Generator {g}: q_min={g.Qmin} MVAR, q_max={g.Qmax} MVAR")
    print()

    print("Branches:")
    for b in grid.branches:
        print(f" - {b}:")
        print(f"   R = {round(b.R, 4)} pu")
        print(f"   X = {round(b.X, 4)} pu")
        print(f"   X/R = {round(b.X/b.R, 1)}")
        print(f"   G = {round(b.G, 4)} pu")
        print(f"   B = {round(b.B, 4)} pu")
    print()

    print("Transformer types:")
    for t in grid.transformer_types:
        print(
            f" - {t}: Copper losses={int(t.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%"
        )
    print()

    print("Losses:")
    for i in range(len(grid.branches)):
        print(
            f" - {grid.branches[i]}: losses={1000*round(power_flow.results.losses[i], 3)} kVA"
        )
    print()

    equal = True
    for i in range(len(approx_volt)):
        if approx_volt[i] != solution[i]:
            equal = False

    assert equal
Example #6
0
def test_basic():
    """
    Basic GridCal test, also useful for a basic tutorial. In this case the
    magnetizing branch of the transformers is neglected by inputting 1e-20
    excitation current and iron core losses.
    The results are identical to ETAP's, which always uses this assumption in
    balanced load flow calculations.
    """
    grid = MultiCircuit(name=test_name)
    grid.Sbase = Sbase
    grid.time_profile = None
    grid.logger = list()

    # Create buses
    POI = Bus(
        name="POI",
        vnom=100,  #kV
        is_slack=True)
    grid.add_bus(POI)

    B_C3 = Bus(name="B_C3", vnom=10)  #kV
    grid.add_bus(B_C3)

    B_MV_M32 = Bus(name="B_MV_M32", vnom=10)  #kV
    grid.add_bus(B_MV_M32)

    B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6)  #kV
    grid.add_bus(B_LV_M32)

    # Create voltage controlled generators (or slack, a.k.a. swing)
    UT = ControlledGenerator(name="Utility")
    UT.bus = POI
    grid.add_controlled_generator(POI, UT)

    # Create static generators (with fixed power factor)
    M32 = StaticGenerator(name="M32", power=4.2 + 0.0j)  # MVA (complex)
    M32.bus = B_LV_M32
    grid.add_static_generator(B_LV_M32, M32)

    # Create transformer types
    s = 5  # MVA
    z = 8  # %
    xr = 40
    SS = TransformerType(
        name="SS",
        hv_nominal_voltage=100,  # kV
        lv_nominal_voltage=10,  # kV
        nominal_power=s,
        copper_losses=complexe(z, xr).real * s * 1000 / Sbase,
        iron_losses=1e-20,
        no_load_current=1e-20,
        short_circuit_voltage=z)
    grid.add_transformer_type(SS)

    s = 5  # MVA
    z = 6  # %
    xr = 20
    PM = TransformerType(
        name="PM",
        hv_nominal_voltage=10,  # kV
        lv_nominal_voltage=0.6,  # kV
        nominal_power=s,
        copper_losses=complexe(z, xr).real * s * 1000 / Sbase,
        iron_losses=1e-20,
        no_load_current=1e-20,
        short_circuit_voltage=z)
    grid.add_transformer_type(PM)

    # Create branches
    X_C3 = Branch(bus_from=POI,
                  bus_to=B_C3,
                  name="X_C3",
                  branch_type=BranchType.Transformer,
                  template=SS)
    grid.add_branch(X_C3)

    C_M32 = Branch(bus_from=B_C3,
                   bus_to=B_MV_M32,
                   name="C_M32",
                   r=0.784,
                   x=0.174)
    grid.add_branch(C_M32)

    X_M32 = Branch(bus_from=B_MV_M32,
                   bus_to=B_LV_M32,
                   name="X_M32",
                   branch_type=BranchType.Transformer,
                   template=PM)
    grid.add_branch(X_M32)

    # Apply templates (device types)
    grid.apply_all_branch_types()

    print("Buses:")
    for i, b in enumerate(grid.buses):
        print(f" - bus[{i}]: {b}")
    print()

    grid.compile()
    options = PowerFlowOptions(SolverType.LM,
                               verbose=True,
                               robust=True,
                               initialize_with_existing_solution=True,
                               multi_core=True,
                               control_q=True,
                               tolerance=1e-6,
                               max_iter=99)

    power_flow = PowerFlow(grid, options)
    power_flow.run()

    approx_volt = [round(100 * abs(v), 1) for v in power_flow.results.voltage]
    solution = [
        100.0, 99.6, 102.7, 102.9
    ]  # Expected solution from GridCal and ETAP 16.1.0, for reference

    print()
    print(f"Test: {test_name}")
    print(f"Results:  {approx_volt}")
    print(f"Solution: {solution}")
    print()

    print("Controlled generators:")
    for g in grid.get_controlled_generators():
        print(f" - Generator {g}: q_min={g.Qmin}pu, q_max={g.Qmax}pu")
    print()

    print("Branches:")
    for b in grid.branches:
        print(f" - {b}:")
        print(f"   R = {round(b.R, 4)} pu")
        print(f"   X = {round(b.X, 4)} pu")
        print(f"   X/R = {round(b.X/b.R, 1)}")
        print(f"   G = {round(b.G, 4)} pu")
        print(f"   B = {round(b.B, 4)} pu")
    print()

    print("Transformer types:")
    for t in grid.transformer_types:
        print(
            f" - {t}: Copper losses={int(t.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%"
        )
    print()

    print("Losses:")
    for i in range(len(grid.branches)):
        print(
            f" - {grid.branches[i]}: losses={1000*round(power_flow.results.losses[i], 3)} kVA"
        )
    print()

    equal = True
    for i in range(len(approx_volt)):
        if approx_volt[i] != solution[i]:
            equal = False

    assert equal
Example #7
0
class Optimize(QThread):
    progress_signal = pyqtSignal(float)
    progress_text = pyqtSignal(str)
    done_signal = pyqtSignal()

    def __init__(self,
                 circuit: MultiCircuit,
                 options: PowerFlowOptions,
                 max_iter=1000):
        """
        Constructor
        Args:
            circuit: Grid to cascade
            options: Power flow Options
            max_iter: max iterations
        """

        QThread.__init__(self)

        self.circuit = circuit

        self.options = options

        self.__cancel__ = False

        # initialize the power flow
        self.power_flow = PowerFlow(self.circuit, self.options)

        self.max_eval = max_iter
        n = len(self.circuit.buses)
        m = len(self.circuit.branches)

        # the dimension is the number of nodes
        self.dim = n

        # results
        self.results = MonteCarloResults(n, m, self.max_eval)

        # variables for the optimization
        self.xlow = zeros(n)  # lower bounds
        self.xup = ones(n)
        self.info = ""  # info
        self.integer = array([])  # integer variables
        self.continuous = arange(0, n, 1)  # continuous variables
        self.solution = None
        self.optimization_values = None
        self.it = 0

        # compile
        # compile circuits
        self.numerical_circuit = self.circuit.compile()
        self.numerical_input_islands = self.numerical_circuit.compute()

    def objfunction(self, x):
        """
        Objective function to run
        :param x: combinations of values between 0~1
        :return: objective function value, the average voltage in this case
        """

        # For every circuit, run the time series
        for numerical_island in self.numerical_input_islands:
            # sample from the CDF give the vector x of values in [0, 1]
            # c.sample_at(x)
            monte_carlo_input = make_monte_carlo_input(numerical_island)
            mc_time_series = monte_carlo_input.get_at(x)

            Y, I, S = mc_time_series.get_at(t=0)

            #  run the sampled values
            # res = self.power_flow.run_at(0, mc=True)
            res = self.power_flow.run_pf(circuit=numerical_island,
                                         Vbus=numerical_island.Vbus,
                                         Sbus=S,
                                         Ibus=I)

            # Y, I, S = circuit.mc_time_series.get_at(0)
            self.results.S_points[self.it,
                                  numerical_island.original_bus_idx] = S
            self.results.V_points[
                self.it, numerical_island.original_bus_idx] = res.voltage[
                    numerical_island.original_bus_idx]
            self.results.I_points[
                self.it, numerical_island.original_branch_idx] = res.Ibranch[
                    numerical_island.original_branch_idx]
            self.results.loading_points[
                self.it, numerical_island.original_branch_idx] = res.loading[
                    numerical_island.original_branch_idx]

        self.it += 1
        prog = self.it / self.max_eval * 100
        # self.progress_signal.emit(prog)

        f = abs(self.results.V_points[self.it - 1, :].sum()) / self.dim
        # print(prog, ' % \t', f)

        return f

    def run(self):
        """
        Run the optimization
        @return: Nothing
        """
        self.it = 0
        n = len(self.circuit.buses)
        m = len(self.circuit.branches)
        self.xlow = zeros(n)  # lower bounds
        self.xup = ones(n)  # upper bounds
        self.progress_signal.emit(0.0)
        self.progress_text.emit('Running stochastic voltage collapse...')
        self.results = MonteCarloResults(n, m, self.max_eval)

        # (1) Optimization problem
        # print(data.info)

        # (2) Experimental design
        # Use a symmetric Latin hypercube with 2d + 1 samples
        exp_des = SymmetricLatinHypercube(dim=self.dim, npts=2 * self.dim + 1)

        # (3) Surrogate model
        # Use a cubic RBF interpolant with a linear tail
        surrogate = RBFInterpolant(kernel=CubicKernel,
                                   tail=LinearTail,
                                   maxp=self.max_eval)

        # (4) Adaptive sampling
        # Use DYCORS with 100d candidate points
        adapt_samp = CandidateDYCORS(data=self, numcand=100 * self.dim)

        # Use the serial controller (uses only one thread)
        controller = SerialController(self.objfunction)

        # (5) Use the sychronous strategy without non-bound constraints
        strategy = SyncStrategyNoConstraints(worker_id=0,
                                             data=self,
                                             maxeval=self.max_eval,
                                             nsamples=1,
                                             exp_design=exp_des,
                                             response_surface=surrogate,
                                             sampling_method=adapt_samp)
        controller.strategy = strategy

        # Run the optimization strategy
        result = controller.run()

        # Print the final result
        print('Best value found: {0}'.format(result.value))
        print('Best solution found: {0}'.format(
            np.array_str(result.params[0],
                         max_line_width=np.inf,
                         precision=5,
                         suppress_small=True)))
        self.solution = result.params[0]

        # Extract function values from the controller
        self.optimization_values = np.array(
            [o.value for o in controller.fevals])

        # send the finnish signal
        self.progress_signal.emit(0.0)
        self.progress_text.emit('Done!')
        self.done_signal.emit()

    def plot(self, ax=None):
        """
        Plot the optimization convergence
        """
        clr = np.array([
            '#2200CC', '#D9007E', '#FF6600', '#FFCC00', '#ACE600', '#0099CC',
            '#8900CC', '#FF0000', '#FF9900', '#FFFF00', '#00CC01', '#0055CC'
        ])
        if self.optimization_values is not None:
            max_eval = len(self.optimization_values)

            if ax is None:
                f, ax = plt.subplots()
            # Points
            ax.scatter(np.arange(0, max_eval),
                       self.optimization_values,
                       color=clr[6])
            # Best value found
            ax.plot(np.arange(0, max_eval),
                    np.minimum.accumulate(self.optimization_values),
                    color=clr[1],
                    linewidth=3.0)
            ax.set_xlabel('Evaluations')
            ax.set_ylabel('Function Value')
            ax.set_title('Optimization convergence')

    def cancel(self):
        """
        Cancel the simulation
        """
        self.__cancel__ = True
        self.progress_signal.emit(0.0)
        self.progress_text.emit('Cancelled')
        self.done_signal.emit()