Skip to content

Generator Perturbations

Base Classes

GenerationGenerator

Bases: ABC

Abstract base class for applying perturbations to generator elements in a network.

Source code in gridfm_datakit/perturbations/generator_perturbation.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class GenerationGenerator(ABC):
    """Abstract base class for applying perturbations to generator elements
    in a network."""

    def __init__(self) -> None:
        """Initialize the generation generator."""
        pass

    @abstractmethod
    def generate(
        self,
        example_generator: Generator[Network, None, None],
    ) -> Union[Generator[Network, None, None], List[Network]]:
        """Generate generation perturbations.

        Args:
            example_generator: A generator producing example (load/topology/generation)
                scenarios to which generator cost perturbations are added.

        Yields:
            A generation-perturbed scenario.
        """
        pass
__init__()

Initialize the generation generator.

Source code in gridfm_datakit/perturbations/generator_perturbation.py
12
13
14
def __init__(self) -> None:
    """Initialize the generation generator."""
    pass
generate(example_generator) abstractmethod

Generate generation perturbations.

Parameters:

Name Type Description Default
example_generator Generator[Network, None, None]

A generator producing example (load/topology/generation) scenarios to which generator cost perturbations are added.

required

Yields:

Type Description
Union[Generator[Network, None, None], List[Network]]

A generation-perturbed scenario.

Source code in gridfm_datakit/perturbations/generator_perturbation.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@abstractmethod
def generate(
    self,
    example_generator: Generator[Network, None, None],
) -> Union[Generator[Network, None, None], List[Network]]:
    """Generate generation perturbations.

    Args:
        example_generator: A generator producing example (load/topology/generation)
            scenarios to which generator cost perturbations are added.

    Yields:
        A generation-perturbed scenario.
    """
    pass

Concrete Implementations

NoGenPerturbationGenerator

Bases: GenerationGenerator

Generator that yields the original network generator without any perturbations.

Source code in gridfm_datakit/perturbations/generator_perturbation.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class NoGenPerturbationGenerator(GenerationGenerator):
    """Generator that yields the original network generator without any perturbations."""

    def __init__(self):
        """Initialize the no-perturbation generator"""
        pass

    def generate(
        self,
        example_generator: Generator[Network, None, None],
    ) -> Generator[Network, None, None]:
        """Yield the original examples without any perturbations.

        Args:
            example_generator: A generator producing example (load/topology/generation)
                scenarios to which generator cost perturbations should be applied.

        Yields:
            The original example produced by the example_generator.
        """
        for example in example_generator:
            yield example
__init__()

Initialize the no-perturbation generator

Source code in gridfm_datakit/perturbations/generator_perturbation.py
36
37
38
def __init__(self):
    """Initialize the no-perturbation generator"""
    pass
generate(example_generator)

Yield the original examples without any perturbations.

Parameters:

Name Type Description Default
example_generator Generator[Network, None, None]

A generator producing example (load/topology/generation) scenarios to which generator cost perturbations should be applied.

required

Yields:

Type Description
Network

The original example produced by the example_generator.

Source code in gridfm_datakit/perturbations/generator_perturbation.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def generate(
    self,
    example_generator: Generator[Network, None, None],
) -> Generator[Network, None, None]:
    """Yield the original examples without any perturbations.

    Args:
        example_generator: A generator producing example (load/topology/generation)
            scenarios to which generator cost perturbations should be applied.

    Yields:
        The original example produced by the example_generator.
    """
    for example in example_generator:
        yield example

PermuteGenCostGenerator

Bases: GenerationGenerator

Class for permuting generator costs.

This class is for generating different generation scenarios by permuting cost coefficients between and among generators of power grid networks. Only generators with non-zero linear (c1) or quadratic (c2) cost coefficients are permuted, preserving zero-cost and constant-only generators.

Source code in gridfm_datakit/perturbations/generator_perturbation.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
class PermuteGenCostGenerator(GenerationGenerator):
    """Class for permuting generator costs.

    This class is for generating different generation scenarios
    by permuting cost coefficients between and among generators
    of power grid networks. Only generators with non-zero linear (c1)
    or quadratic (c2) cost coefficients are permuted, preserving
    zero-cost and constant-only generators.
    """

    def __init__(self, base_net: Network) -> None:
        """
        Initialize the gen-cost permuation generator.

        Args:
            base_net: The base power network.
        """
        self.base_net = base_net
        self.num_gens = base_net.gens.shape[0]  # acount for deactivated generators
        assert np.all(base_net.gencosts[:, NCOST] == base_net.gencosts[:, NCOST][0]), (
            "All generators must have the same number of cost coefficients"
        )

        # Identify generators to permute (skip zero-cost and constant-only cost)
        costs = base_net.gencosts[
            :,
            COST:,
        ]  # all cost coefficients [c2, c1, c0] for NCOST=3
        # Keep only generators with non-constant terms (c1, c2, ...) non-zero
        # costs[:, :-1] excludes the last coefficient (c0, the constant term)
        self.permutable_mask = np.any(costs[:, :-1] != 0, axis=1)
        self.permutable_indices = np.where(self.permutable_mask)[0]

    def generate(
        self,
        example_generator: Generator[Network, None, None],
    ) -> Generator[Network, None, None]:
        """Generate a network with permuted generator cost coefficients.

        Args:
            example_generator: A generator producing example
                (load/topology/generation) scenarios to which generator
                cost coefficient permutations should be applied.

        Yields:
            An example scenario with cost coeffiecients in the
            poly_cost table permuted (only for dispatchable generators)
        """
        for scenario in example_generator:
            # Only permute the selected generators
            new_idx = np.random.permutation(self.permutable_indices)
            # Permute the rows (generators) of the cost coefficients (and NCOST, although we assume it is the same for all generators)
            scenario.gencosts[self.permutable_indices, NCOST:] = scenario.gencosts[
                new_idx,
                NCOST:,
            ]
            yield scenario
__init__(base_net)

Initialize the gen-cost permuation generator.

Parameters:

Name Type Description Default
base_net Network

The base power network.

required
Source code in gridfm_datakit/perturbations/generator_perturbation.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def __init__(self, base_net: Network) -> None:
    """
    Initialize the gen-cost permuation generator.

    Args:
        base_net: The base power network.
    """
    self.base_net = base_net
    self.num_gens = base_net.gens.shape[0]  # acount for deactivated generators
    assert np.all(base_net.gencosts[:, NCOST] == base_net.gencosts[:, NCOST][0]), (
        "All generators must have the same number of cost coefficients"
    )

    # Identify generators to permute (skip zero-cost and constant-only cost)
    costs = base_net.gencosts[
        :,
        COST:,
    ]  # all cost coefficients [c2, c1, c0] for NCOST=3
    # Keep only generators with non-constant terms (c1, c2, ...) non-zero
    # costs[:, :-1] excludes the last coefficient (c0, the constant term)
    self.permutable_mask = np.any(costs[:, :-1] != 0, axis=1)
    self.permutable_indices = np.where(self.permutable_mask)[0]
generate(example_generator)

Generate a network with permuted generator cost coefficients.

Parameters:

Name Type Description Default
example_generator Generator[Network, None, None]

A generator producing example (load/topology/generation) scenarios to which generator cost coefficient permutations should be applied.

required

Yields:

Type Description
Network

An example scenario with cost coeffiecients in the

Network

poly_cost table permuted (only for dispatchable generators)

Source code in gridfm_datakit/perturbations/generator_perturbation.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def generate(
    self,
    example_generator: Generator[Network, None, None],
) -> Generator[Network, None, None]:
    """Generate a network with permuted generator cost coefficients.

    Args:
        example_generator: A generator producing example
            (load/topology/generation) scenarios to which generator
            cost coefficient permutations should be applied.

    Yields:
        An example scenario with cost coeffiecients in the
        poly_cost table permuted (only for dispatchable generators)
    """
    for scenario in example_generator:
        # Only permute the selected generators
        new_idx = np.random.permutation(self.permutable_indices)
        # Permute the rows (generators) of the cost coefficients (and NCOST, although we assume it is the same for all generators)
        scenario.gencosts[self.permutable_indices, NCOST:] = scenario.gencosts[
            new_idx,
            NCOST:,
        ]
        yield scenario

PerturbGenCostGenerator

Bases: GenerationGenerator

Class for perturbing generator cost.

This class is for generating different generation scenarios by randomly perturbing cost coefficients of generators in a power network by multiplying with a scaling factor sampled from a uniform distribution. Only generators with non-zero linear (c1) or quadratic (c2) cost coefficients are perturbed, preserving zero-cost and constant-only generators.

Source code in gridfm_datakit/perturbations/generator_perturbation.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
class PerturbGenCostGenerator(GenerationGenerator):
    """Class for perturbing generator cost.

    This class is for generating different generation scenarios
    by randomly perturbing cost coefficients of generators in a
    power network by multiplying with a scaling factor sampled
    from a uniform distribution. Only generators with non-zero
    linear (c1) or quadratic (c2) cost coefficients are perturbed,
    preserving zero-cost and constant-only generators.
    """

    def __init__(self, base_net: Network, sigma: float) -> None:
        """
        Initialize the gen-cost perturbation generator.

        Args:
            base_net: The base power network.
            sigma: A constant that specifies the range from which to draw
                samples from a uniform distribution to be used as a scaling
                factor for cost coefficient perturbations. The range is
                set as [max([0, 1-sigma]), 1+sigma).
        """
        self.base_net = base_net
        self.num_gens = base_net.gens.shape[0]  # acount for deactivated generators
        # assert all generators have the same number of cost coefficients
        assert np.all(base_net.gencosts[:, NCOST] == base_net.gencosts[:, NCOST][0]), (
            "All generators must have the same number of cost coefficients"
        )
        n_costs = int(base_net.gencosts[:, NCOST][0])
        self.lower = np.max([0.0, 1.0 - sigma])
        self.upper = 1.0 + sigma

        # Mask generators to perturb (skip zero-cost or constant-only)
        costs = base_net.gencosts[
            :,
            COST:,
        ]  # all cost coefficients [c2, c1, c0] for NCOST=3
        # Keep only generators with non-constant terms (c1, c2, ...) non-zero
        # costs[:, :-1] excludes the last coefficient (c0, the constant term)
        self.perturb_mask = np.any(costs[:, :-1] != 0, axis=1)
        self.n_perturbable = np.sum(self.perturb_mask)

        # Sample size for only the perturbable generators
        self.sample_size = [self.n_perturbable, n_costs]

    def generate(
        self,
        example_generator: Generator[Network, None, None],
    ) -> Generator[Network, None, None]:
        """Generate a network with perturbed generator cost coefficients.

        Args:
            example_generator: A generator producing example (load/topology) scenarios
                to which generator cost coefficient perturbations should be added.

        Yields:
            An example scenario with cost coeffiecients in the poly_cost
            table perturbed by multiplying with a scaling factor (only for dispatchable generators).
        """
        for example in example_generator:
            # Generate scaling factors only for generators that can be perturbed
            scale_fact = np.random.uniform(
                low=self.lower,
                high=self.upper,
                size=self.sample_size,
            )
            # Apply scaling only to selected generators
            example.gencosts[self.perturb_mask, COST:] = (
                example.gencosts[self.perturb_mask, COST:] * scale_fact
            )
            yield example
__init__(base_net, sigma)

Initialize the gen-cost perturbation generator.

Parameters:

Name Type Description Default
base_net Network

The base power network.

required
sigma float

A constant that specifies the range from which to draw samples from a uniform distribution to be used as a scaling factor for cost coefficient perturbations. The range is set as [max([0, 1-sigma]), 1+sigma).

required
Source code in gridfm_datakit/perturbations/generator_perturbation.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def __init__(self, base_net: Network, sigma: float) -> None:
    """
    Initialize the gen-cost perturbation generator.

    Args:
        base_net: The base power network.
        sigma: A constant that specifies the range from which to draw
            samples from a uniform distribution to be used as a scaling
            factor for cost coefficient perturbations. The range is
            set as [max([0, 1-sigma]), 1+sigma).
    """
    self.base_net = base_net
    self.num_gens = base_net.gens.shape[0]  # acount for deactivated generators
    # assert all generators have the same number of cost coefficients
    assert np.all(base_net.gencosts[:, NCOST] == base_net.gencosts[:, NCOST][0]), (
        "All generators must have the same number of cost coefficients"
    )
    n_costs = int(base_net.gencosts[:, NCOST][0])
    self.lower = np.max([0.0, 1.0 - sigma])
    self.upper = 1.0 + sigma

    # Mask generators to perturb (skip zero-cost or constant-only)
    costs = base_net.gencosts[
        :,
        COST:,
    ]  # all cost coefficients [c2, c1, c0] for NCOST=3
    # Keep only generators with non-constant terms (c1, c2, ...) non-zero
    # costs[:, :-1] excludes the last coefficient (c0, the constant term)
    self.perturb_mask = np.any(costs[:, :-1] != 0, axis=1)
    self.n_perturbable = np.sum(self.perturb_mask)

    # Sample size for only the perturbable generators
    self.sample_size = [self.n_perturbable, n_costs]
generate(example_generator)

Generate a network with perturbed generator cost coefficients.

Parameters:

Name Type Description Default
example_generator Generator[Network, None, None]

A generator producing example (load/topology) scenarios to which generator cost coefficient perturbations should be added.

required

Yields:

Type Description
Network

An example scenario with cost coeffiecients in the poly_cost

Network

table perturbed by multiplying with a scaling factor (only for dispatchable generators).

Source code in gridfm_datakit/perturbations/generator_perturbation.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def generate(
    self,
    example_generator: Generator[Network, None, None],
) -> Generator[Network, None, None]:
    """Generate a network with perturbed generator cost coefficients.

    Args:
        example_generator: A generator producing example (load/topology) scenarios
            to which generator cost coefficient perturbations should be added.

    Yields:
        An example scenario with cost coeffiecients in the poly_cost
        table perturbed by multiplying with a scaling factor (only for dispatchable generators).
    """
    for example in example_generator:
        # Generate scaling factors only for generators that can be perturbed
        scale_fact = np.random.uniform(
            low=self.lower,
            high=self.upper,
            size=self.sample_size,
        )
        # Apply scaling only to selected generators
        example.gencosts[self.perturb_mask, COST:] = (
            example.gencosts[self.perturb_mask, COST:] * scale_fact
        )
        yield example