Beispiel #1
0
    def addDualVar(
        self,
        lb=0.0,
        ub=gurobipy.GRB.INFINITY,
        obj=0.0,
        vtype=gurobipy.GRB.CONTINUOUS,
        uncertainty=None,
        random_state=None,
        name="",
    ):

        random_state = check_random_state(random_state)
        #check uncertainty
        if uncertainty is not None:
            uncertainty = self._check_uncertainty(uncertainty, 0, 1)
            if callable(uncertainty):
                samples = []
                probability = []
                for _ in range(self.dual_n_samples):
                    samples.append(uncertainty(random_state))
                    probability.append(1 / self.dual_n_samples)
                self.dual_probability = probability  #force continous uncertainty to uniform discretization
            else:
                samples = list(uncertainty)

        #set up obj values: with or without uncertainty
        if uncertainty is not None:
            obj = [
                samples[i] * self.dual_probability[i]
                for i in range(self.dual_n_samples)
            ]
        if uncertainty is None:
            obj = [
                obj * self.dual_probability[i]
                for i in range(self.dual_n_samples)
            ]

        var = self._model.addVars(self.dual_n_samples,
                                  lb=lb,
                                  ub=ub,
                                  obj=obj,
                                  vtype=vtype,
                                  name=name)
        self._model.update()

        return var
    def run_single(self,
                 pv,
                 jobs,
                 random_state = None,
                 query = None,
                 query_dual = None,
                 query_stage_cost = False,
                 stage_cost = None,
                 solution = None,
                 solution_dual = None
            ):
        random_state = check_random_state(random_state)

        for j in jobs:
            sample_path_idx = (self.sample_path_idx[j]
                if self.sample_path_idx is not None else None)
            state = 0
            result = self.solver._forward(
                       random_state = random_state,
                       sample_path_idx = sample_path_idx,
                       solve_true = self.solve_true,
                       query = query,
                       query_dual = query_dual,
                       query_stage_cost= query_stage_cost,
                )
            if query is not None:
                for item in query:
                    for i in range(len(solution[item][0])):
                        solution[item][j][i] = result['solution'][item][i]
            if query_dual is not None:
                for item in solution_dual:
                    for i in range(len(solution_dual[item][0])):
                        solution_dual[item][j][i] = result['solution_dual'][item][i]
            if query_stage_cost:
                for i in range(len(stage_cost[0])):
                    stage_cost[j][i] = result['stage_cost'][i]
            pv[j] = result['pv']
Beispiel #3
0
    def discretize(self,
                   n_samples=None,
                   random_state=None,
                   replace=True,
                   n_Markov_states=None,
                   method='SA',
                   n_sample_paths=None,
                   Markov_states=None,
                   transition_matrix=None,
                   int_flag=0):
        """Discretize Markovian continuous uncertainty by k-means or (robust)
        stochasitic approximation.

        Parameters
        ----------
        n_samples: int, optional, default=None
            number of i.i.d. samples to generate for stage-wise independent
            randomness.

        random_state: None | int | instance of RandomState, optional, default=None
            If int, random_state is the seed used by the
            random number generator;
            If RandomState instance, random_state is the
            random number generator;
            If None, the random number generator is the
            RandomState instance used by numpy.random.

        replace: bool, optional, default=True
            Indicates generating i.i.d. samples with/without replacement for
            stage-wise independent randomness.

        n_Markov_states: list | int, optional, default=None
            If list, it specifies different dimensions of Markov state space
            over time. Length of the list should equal length of the Markovian
            uncertainty.
            If int, it specifies dimensions of Markov state space.
            Note: If the uncertainties are int, trained Markov states will be
            rounded to integers, and duplicates will be removed. In such cases,
            there is no guaranttee that the number of Markov states is n_Markov_states.

        method: binary, optional, default=0
            'input': the approximating Markov chain is given by user input (
            through specifying Markov_states and transition_matrix)
            'SAA': use k-means to train Markov chain.
            'SA': use stochastic approximation to train Markov chain.
            'RSA': use robust stochastic approximation to train Markov chain.

        n_sample_paths: int, optional, default=None
            number of sample paths to train the Markov chain.

        Markov_states/transition_matrix: matrix-like, optional, default=None
            The user input of approximating Markov chain.
        """
        if n_samples is not None:
            if isinstance(n_samples, (numbers.Integral, numpy.integer)):
                if n_samples < 1:
                    raise ValueError("n_samples should be bigger than zero!")
                n_samples = ([1] + [n_samples] * (self.T - 1))
            elif isinstance(n_samples, (abc.Sequence, numpy.ndarray)):
                if len(n_samples) != self.T:
                    raise ValueError(
                        "n_samples list should be of length {} rather than {}!"
                        .format(self.T, len(n_samples)))
                if n_samples[0] != 1:
                    raise ValueError(
                        "The first stage model should be deterministic!")
            else:
                raise ValueError("Invalid input of n_samples!")
            # discretize stage-wise independent continuous distribution
            random_state = check_random_state(random_state)
            for t in range(1, self.T):
                self.models[t]._discretize(n_samples[t], random_state, replace)
        if n_Markov_states is None and method != 'input': return
        if method == 'input' and (Markov_states is None
                                  or transition_matrix is None):
            return
        if n_Markov_states is not None:
            if isinstance(n_Markov_states, (numbers.Integral, numpy.integer)):
                if n_Markov_states < 1:
                    raise ValueError(
                        "n_Markov_states should be bigger than zero!")
                n_Markov_states = ([1] + [n_Markov_states] * (self.T - 1))
            elif isinstance(n_Markov_states, (abc.Sequence, numpy.ndarray)):
                if len(n_Markov_states) != self.T:
                    raise ValueError(
                        "n_Markov_states list should be of length {} rather than {}!"
                        .format(self.T, len(n_Markov_states)))
                if n_Markov_states[0] != 1:
                    raise ValueError(
                        "The first stage model should be deterministic!")
            else:
                raise ValueError("Invalid input of n_Markov_states!")
        from msppy.discretize import Markovian
        if method in ['RSA', 'SA', 'SAA']:
            markovian = Markovian(
                f=self.Markovian_uncertainty,
                n_Markov_states=n_Markov_states,
                n_sample_paths=n_sample_paths,
                int_flag=int_flag,
            )
        if method in ['RSA', 'SA', 'SAA']:
            self.Markov_states, self.transition_matrix = getattr(
                markovian, method)()
        elif method == 'input':
            dim_Markov_states, n_Markov_states = (
                check_Markov_states_and_transition_matrix(
                    Markov_states=Markov_states,
                    transition_matrix=transition_matrix,
                    T=self.T,
                ))
            if dim_Markov_states != self.dim_Markov_states:
                raise ValueError(
                    "The dimension of the given sample path " +
                    "generator is not the same as the given Markov chain " +
                    "approximation!")
            self.Markov_states = Markov_states
            self.transition_matrix = [
                numpy.array(item) for item in transition_matrix
            ]
        self._flag_discrete = 1
        self.n_Markov_states = n_Markov_states
        if method in ['RSA', 'SA', 'SAA']:
            return markovian
Beispiel #4
0
    def run(
            self,
            n_simulations,
            percentile=95,
            query=None,
            query_stage_cost=False,
            random_state=None,):
        """Run a Monte Carlo simulation to evaluate the policy on the
        approximation model.

        Parameters
        ----------
        n_simulations: int/-1
            If int: the number of simulations;
            If -1: exhuastive evaluation.

        query: list, optional (default=None)
            The names of variables that are intended to query.

        query_stage_cost: bool, optional (default=False)
            Whether to query values of individual stage costs.

        percentile: float, optional (default=95)
            The percentile used to compute the confidence interval.

        random_state: int, RandomState instance or None, optional
            (default=None)
            If int, random_state is the seed used by the random number
            generator;
            If RandomState instance, random_state is the random number
            generator;
            If None, the random number generator is the RandomState
            instance used by numpy.random.
        """
        random_state = check_random_state(random_state)
        query = [] if query is None else list(query)
        MSP = self.MSP
        if n_simulations == -1:
            n_sample_paths, sample_paths = MSP._enumerate_sample_paths(MSP.T-1)
        else:
            n_sample_paths = n_simulations
        ub = [0] * n_sample_paths
        if query_stage_cost:
            stage_cost = [
                [0 for _ in range(n_sample_paths)] for _ in range(MSP.T)
            ]
        solution = {item: [[] for _ in range(MSP.T)] for item in query}
        # forward Sampling
        for j in range(n_sample_paths):
            if n_simulations == -1:
                sample_path = sample_paths[j]
            state = 0
            # time loop
            for t in range(MSP.T):
                if MSP.n_Markov_states == 1:
                    m = MSP.models[t]
                else:
                    if n_simulations == -1:
                        m = MSP.models[t][sample_path[1][t]]
                    else:
                        if t == 0:
                            m = MSP.models[t][0]
                        else:
                            state = random_state.choice(
                                range(MSP.n_Markov_states[t]),
                                p=MSP.transition_matrix[t][state],
                            )
                            m = MSP.models[t][state]
                if t > 0:
                    m._update_link_constrs(forward_solution)
                    if MSP.n_Markov_states == 1:
                        scenario_index = (
                            sample_path[t]
                            if n_simulations == -1
                            else rand_int(
                                m.n_samples, random_state, m.probability
                            )
                        )
                    else:
                        scenario_index = (
                            sample_path[0][t]
                            if n_simulations == -1
                            else rand_int(
                                m.n_samples, random_state, m.probability
                            )
                        )
                    m._update_uncertainty(scenario_index)
                m.optimize()
                if m.status not in [2,11]:
                    m.write_infeasible_model("evaluation_" + str(m.modelName))
                forward_solution = MSP._get_forward_solution(m, t)
                for var in m.getVars():
                    if var.varName in query:
                        solution[var.varName][t].append(var.X)
                if query_stage_cost:
                    stage_cost[t][i] = MSP._get_stage_cost(m, t)
                ub[j] += MSP._get_stage_cost(m, t)
            #! time loop
        #! forward Sampling
        self.pv = ub
        if n_simulations == -1:
            self.epv = numpy.dot(
                ub,
                [
                    MSP._compute_weight_sample_path(sample_paths[j])
                    for j in range(n_sample_paths)
                ],
            )
        if n_simulations not in [-1,1]:
            self.CI = compute_CI(ub, percentile)
        self._compute_gap()
        self.solution = {k: pandas.DataFrame(v) for k, v in solution.items()}
        if query_stage_cost:
            self.stage_cost = pandas.DataFrame(stage_cost)
Beispiel #5
0
    def run(
            self,
            n_simulations,
            query=None,
            query_stage_cost=False,
            random_state=None,
            percentile=95):
        """Run a Monte Carlo simulation to evaluate a policy on the true problem.

        Parameters
        ----------
        n_simulations: int
            The number of simulations.

        query: list, optional (default=None)
            The names of variables that are intended to query.

        percentile: float, optional (default=95)
            The percentile used to compute the confidence interval.

        query_stage_cost: bool, optional (default=False)
            Whether to query values of individual stage costs.

        random_state: int, RandomState instance or None, optional
            (default=None)
            If int, random_state is the seed used by the random number
            generator;
            If RandomState instance, random_state is the random number
            generator;
            If None, the random number generator is the RandomState
            instance used by numpy.random.
        """
        MSP = self.MSP
        if MSP.__class__.__name__ == 'MSIP':
            MSP._back_binarize()
        # discrete finite model should call evaluate instead
        if (
            MSP._type in ["stage-wise independent", "Markov chain"]
            and MSP._individual_type == "original"
            and not hasattr(MSP,"bin_stage")
        ):
            return super().run(
                n_simulations=n_simulations,
                query=query,
                query_stage_cost=query_stage_cost,
                percentile=percentile,
                random_state=random_state,
            )
        if n_simulations <= 0:
            raise ValueError("number of simulations must be bigger than 0")
        random_state = check_random_state(random_state)
        if MSP._type == "Markovian":
            samples = MSP.Markovian_uncertainty(random_state,n_simulations)
            label_all = numpy.zeros([n_simulations,MSP.T],dtype=int)
            for t in range(1,MSP.T):
                dist = numpy.empty([n_simulations,MSP.n_Markov_states[t]])
                for idx, markov_state in enumerate(MSP.Markov_states[t]):
                    temp = samples[:,t,:] - markov_state
                    dist[:,idx] = numpy.sum(temp**2, axis=1)
                label_all[:,t] = numpy.argmin(dist,axis=1)
        query = [] if query is None else list(query)
        ub = [0] * n_simulations
        if query_stage_cost:
            stage_cost = [[0 for _ in range(n_simulations)] for _ in range(MSP.T)]
        solution = {item: [[] for _ in range(MSP.T)] for item in query}
        # forward Sampling
        for j in range(n_simulations):
            # Markov chain uncertainty state
            if MSP._type == "Markov chain":
                state = 0
            # time loop
            for t in range(MSP.T):
                # sample Markovian uncertainties
                if MSP._type == "Markovian":
                    if t == 0:
                        m = MSP.models[t][0]
                    else:
                        # use the model with the closest markov state
                        m = MSP.models[t][label_all[j][t]]
                        # update Markovian uncertainty
                        m._update_uncertainty_dependent(samples[j][t])
                elif MSP._type == "Markov chain":
                    if t == 0:
                        m = MSP.models[t][0]
                    else:
                        state = random_state.choice(
                            range(MSP.n_Markov_states[t]),
                            p=MSP.transition_matrix[t][state],
                        )
                        m = MSP.models[t][state]
                else:
                    m = MSP.models[t]
                # sample independent uncertainties
                if t > 0:
                    if m._type == "continuous":
                        m._sample_uncertainty(random_state)
                    elif m._flag_discrete == 1:
                        m._update_uncertainty_discrete(
                            rand_int(
                                m.n_samples_discrete,random_state, m.probability)
                        )
                    else:
                        m._update_uncertainty(
                            rand_int(m.n_samples, random_state, m.probability)
                        )
                    m._update_link_constrs(forward_solution)
                m.optimize()
                if m.status not in [2,11]:
                    m.write_infeasible_model("evaluation_true_" + str(m.modelName))
                # get solutions
                forward_solution = MSP._get_forward_solution(m, t)
                for var in m.getVars():
                    if var.varName in query:
                        solution[var.varName][t].append(var.X)
                if query_stage_cost:
                    stage_cost[t].append(MSP._get_stage_cost(m, t))
                ub[j] += MSP._get_stage_cost(m, t)
                if MSP._type == "Markovian":
                    m._update_uncertainty_dependent(
                        MSP.Markov_states[t][label_all[j][t]])
            #! end time loop
        #! forward Sampling
        self.solution = {k: pandas.DataFrame(v) for k, v in solution.items()}
        if query_stage_cost:
            self.stage_cost = pandas.DataFrame(stage_cost)
        self.pv = ub
        if n_simulations != 1:
            self.CI = compute_CI(ub, percentile)
Beispiel #6
0
    def addDualStateVar(
        self,
        t,
        lb=0.0,
        ub=gurobipy.GRB.INFINITY,
        obj=0.0,
        vtype=gurobipy.GRB.CONTINUOUS,
        uncertainty=None,
        random_state=None,
        name="",
    ):
        """
      Add state variable

      Parameters:
      ----------
      random_state: integer or numpy.random.RandomState instance
      """
        if t == 0:
            if random_state is None:
                state = self._model.addVar(lb=lb,
                                           ub=ub,
                                           obj=obj,
                                           vtype=vtype,
                                           name=name)
                local_copy = self._model.addVar(
                    name="{}_local_copy".format(name),
                    lb=lb,
                    ub=ub,
                )
                self._model.update()
                self.states += [state]
                self.local_copies += [local_copy]
                self.n_states += 1
                self.find_states += [[state]]
                self.find_n_states += 1

        if t > 0 or random_state is not None:

            random_state = check_random_state(random_state)

            if uncertainty is not None:
                uncertainty = self._check_uncertainty(uncertainty, 0, 1)

                if callable(uncertainty):
                    samples = []
                    probability = []
                    for _ in range(self.dual_n_samples):
                        samples.append(uncertainty(random_state))
                        probability.append(1 / self.dual_n_samples)
                    self.dual_probability = probability
                else:
                    samples = list(uncertainty)

            #set up obj values: with or without uncertainty
            if uncertainty is not None:
                obj = [
                    samples[i] * self.dual_probability[i]
                    for i in range(self.dual_n_samples)
                ]
            if uncertainty is None and len(list([obj])) == 1:
                obj = [
                    obj * self.dual_probability[i]
                    for i in range(self.dual_n_samples)
                ]

            state = self._model.addVars(self.dual_n_samples,
                                        lb=lb,
                                        ub=ub,
                                        obj=obj,
                                        vtype=vtype,
                                        name=name)
            local_copy = self._model.addVar(
                name="{}_local_copy".format(name),
                lb=lb,
                ub=ub,
            )

            self._model.update()
            self.states += state.values()
            self.local_copies += [local_copy]
            self.n_states += self.dual_n_samples
            self.find_states += [state.values()]
            self.find_n_states += 1

        return state, local_copy
Beispiel #7
0
    def addExpecConstr(self,
                       past=None,
                       now=None,
                       var=None,
                       past_coefficient=0.0,
                       now_coefficient=0.0,
                       var_coefficient=0.0,
                       rhs=0.0,
                       plus_penalty_coefficient=0.0,
                       minus_penalty_coefficient=0.0,
                       uncertainty={
                           'rhs': None,
                           'past': None,
                           'var': None,
                           'now': None
                       },
                       random_state=None,
                       sense=None,
                       name='',
                       constant_penalty=None,
                       p1=1.015,
                       p2=1.3):
        """
      Add constraint with expectations of variables

      Parameters
      ----------
      past: a list of var(s)
      past_coefficient: a list of coef(s) of past variable(s)
      uncertainty: dict(default=dict)
      """
        self.p1 = p1
        self.p2 = p2
        self.constant_penalty = constant_penalty

        random_state = check_random_state(random_state)
        #check uncertainty
        for key, value in uncertainty.items():
            if value is not None:
                value = self._check_uncertainty(value, 0, 1)
            if type(key) == str and key.lower() == 'rhs':
                if value is not None:
                    samples = self._discretize_dual_uncertainty(
                        'rhs', value, random_state)
                if value is None:
                    samples = [rhs for _ in range(self.dual_n_samples)]
                self.expec_uncertainty_rhs += [samples]

            if type(key) == str and key.lower() == 'past':
                if value is not None:
                    samples = self._discretize_dual_uncertainty(
                        'past', value, random_state)
                if value is None:
                    samples = [
                        past_coefficient for _ in range(self.dual_n_samples)
                    ]
                self.expec_uncertainty_past += [samples]

            if type(key) == str and key.lower() == 'now':
                if value is not None:
                    now_coefficient = self._discretize_dual_uncertainty(
                        'now', value, random_state)
                if value is None and now_coefficient is not None:
                    now_coefficient = [
                        now_coefficient * self.dual_probability[i]
                        for i in range(self.dual_n_samples)
                    ]
            if type(key) == str and key.lower() == 'var':
                if value is not None:
                    var_coefficient = self._discretize_dual_uncertainty(
                        'var', value, random_state)
                if value is None and var_coefficient is not None:
                    var_coefficient = [
                        var_coefficient * self.dual_probability[i]
                        for i in range(self.dual_n_samples)
                    ]

        if var is not None and now is not None:
            lhs = gurobipy.LinExpr(
                gurobipy.quicksum([
                    now_coefficient[i] * now[i] + var_coefficient[i] * var[i]
                    for i in range(self.dual_n_samples)
                ]) + gurobipy.quicksum([1 * past[i]
                                        for i in range(len(past))]))
        if var is None and now is not None:
            lhs = gurobipy.LinExpr(
                gurobipy.quicksum([
                    now_coefficient[i] * now[i]
                    for i in range(self.dual_n_samples)
                ]) + gurobipy.quicksum([1 * past[i]
                                        for i in range(len(past))]))
        if var is not None and now is None:
            lhs = gurobipy.LinExpr(
                gurobipy.quicksum([
                    var_coefficient[i] * var[i]
                    for i in range(self.dual_n_samples)
                ]) + gurobipy.quicksum([1 * past[i]
                                        for i in range(len(past))]))
        #add penalty
        if plus_penalty_coefficient != 0.0:
            plus_penalty = self._model.addVar(
                lb=0.0,
                obj=-abs(plus_penalty_coefficient),
                name='plus_penalty_' + name)
        if minus_penalty_coefficient != 0.0:
            minus_penalty = self._model.addVar(
                lb=0.0,
                obj=-abs(minus_penalty_coefficient),
                name='minus_penalty_' + name)

        if plus_penalty_coefficient != 0.0 and minus_penalty_coefficient != 0.0:
            lhs = gurobipy.LinExpr(lhs + plus_penalty - minus_penalty)
        elif plus_penalty_coefficient == 0.0 and minus_penalty_coefficient != 0.0:
            lhs = gurobipy.LinExpr(lhs - minus_penalty)
        elif plus_penalty_coefficient != 0.0 and minus_penalty_coefficient == 0.0:
            lhs = gurobipy.LinExpr(lhs + plus_penalty)
        else:
            lhs = gurobipy.LinExpr(lhs)

        constr = self._model.addConstr(lhs=lhs,
                                       sense=sense,
                                       rhs=0.0,
                                       name=name)
        self._model.update()

        self.expec_constrs += [constr]
        self.expec_past += [past]

        return constr
    def run(
            self,
            n_simulations,
            percentile=95,
            query=None,
            query_T = None,
            query_dual=None,
            query_stage_cost=False,
            random_state=None,
            n_processes = 1,):
        """Run a Monte Carlo simulation to evaluate the policy on the
        approximation model.

        Parameters
        ----------
        n_simulations: int/-1
            If int: the number of simulations;
            If -1: exhuastive evaluation.

        query: list, optional (default=None)
            The names of variables that are intended to query.

        query_dual: list, optional (default=None)
            The names of constraints whose dual variables are intended to query.

        query_stage_cost: bool, optional (default=False)
            Whether to query values of individual stage costs.

        percentile: float, optional (default=95)
            The percentile used to compute the confidence interval.

        random_state: int, RandomState instance or None, optional
            (default=None)
            If int, random_state is the seed used by the random number
            generator;
            If RandomState instance, random_state is the random number
            generator;
            If None, the random number generator is the RandomState
            instance used by numpy.random.
        """
        from solver_penalty import SDDPPenalty, SDDPPenalty_infinity
        MSP = self.MSP
        query_T = query_T if query_T else MSP.T
        if not MSP._flag_infinity:
            self.solver = SDDPPenalty(MSP)
            stage = query_T
        else:
            self.solver = SDDPPenalty_infinity(MSP)
            self.solver.forward_T = query_T
            stage = MSP.T-1

        self.n_simulations = n_simulations
        random_state = check_random_state(random_state)
        query = [] if query is None else list(query)
        query_dual = [] if query_dual is None else list(query_dual)
        MSP = self.MSP
        if n_simulations == -1:
            self.n_sample_paths, self.sample_path_idx = MSP._enumerate_sample_paths(query_T-1)
        else:
            self.n_sample_paths = n_simulations
            self.sample_path_idx = None

        self.pv = numpy.zeros(self.n_sample_paths)
        stage_cost = solution = solution_dual = None
        if query_stage_cost:
            stage_cost = [
                multiprocessing.RawArray("d",[0] * (stage))
                for _ in range(self.n_sample_paths)
            ]
        if query is not None:
            solution = {
                item: [
                    multiprocessing.RawArray("d",[0] * (stage))
                    for _ in range(self.n_sample_paths)
                ]
                for item in query
            }
        if query_dual is not None:
            solution_dual = {
                item: [
                    multiprocessing.RawArray("d",[0] * (stage))
                    for _ in range(self.n_sample_paths)
                ]
                for item in query_dual
            }
        n_processes = min(self.n_sample_paths, n_processes)
        jobs = allocate_jobs(self.n_sample_paths, n_processes)
        pv = multiprocessing.Array("d", [0] * self.n_sample_paths)
        procs = [None] * n_processes
        for p in range(n_processes):
            procs[p] = multiprocessing.Process(
                target=self.run_single,
                args=(pv,jobs[p],random_state,query,query_dual,query_stage_cost,stage_cost,
                    solution,solution_dual)
            )
            procs[p].start()
        for proc in procs:
            proc.join()
        if self.n_simulations != 1:
            self.pv = [item for item in pv]
        else:
            self.pv = pv[0]
        if self.n_simulations == -1:
            self.epv = numpy.dot(
                pv,
                [
                    MSP._compute_weight_sample_path(self.sample_path_idx[j])
                    for j in range(self.n_sample_paths)
                ],
            )
        if self.n_simulations not in [-1,1]:
            self.CI = compute_CI(self.pv, percentile)
        self._compute_gap()
        if query is not None:
            self.solution = {
                k: pandas.DataFrame(
                    numpy.array(v)
                ) for k, v in solution.items()
            }
        if query_dual is not None:
            self.solution_dual = {
                k: pandas.DataFrame(
                    numpy.array(v)
                ) for k, v in solution_dual.items()
            }
        if query_stage_cost:
            self.stage_cost = pandas.DataFrame(numpy.array(stage_cost))