openscvx 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of openscvx might be problematic. Click here for more details.
- openscvx/_version.py +2 -2
- openscvx/backend/__init__.py +0 -0
- openscvx/backend/control.py +119 -0
- openscvx/backend/expr.py +62 -0
- openscvx/backend/parameter.py +95 -0
- openscvx/backend/state.py +441 -0
- openscvx/backend/utils.py +27 -0
- openscvx/backend/variable.py +209 -0
- {openscvx-0.2.1.dist-info → openscvx-0.2.2.dist-info}/METADATA +1 -1
- {openscvx-0.2.1.dist-info → openscvx-0.2.2.dist-info}/RECORD +13 -6
- {openscvx-0.2.1.dist-info → openscvx-0.2.2.dist-info}/WHEEL +0 -0
- {openscvx-0.2.1.dist-info → openscvx-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {openscvx-0.2.1.dist-info → openscvx-0.2.2.dist-info}/top_level.txt +0 -0
openscvx/_version.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from openscvx.backend.variable import Variable
|
|
3
|
+
|
|
4
|
+
class Control(Variable):
|
|
5
|
+
"""A class representing the control variables in an optimal control problem.
|
|
6
|
+
|
|
7
|
+
The Control class extends Variable to handle control-specific properties and supports
|
|
8
|
+
both true and augmented control dimensions. It provides methods for appending new control
|
|
9
|
+
variables and accessing subsets of the control vector.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
name (str): Name of the control variable.
|
|
13
|
+
shape (tuple): Shape of the control variable array.
|
|
14
|
+
min (np.ndarray): Minimum bounds for the control variables. Shape: (n_controls,).
|
|
15
|
+
max (np.ndarray): Maximum bounds for the control variables. Shape: (n_controls,).
|
|
16
|
+
guess (np.ndarray): Used to initialize SCP and contains the current SCP solution for the control trajectory. Shape: (n_nodes, n_controls).
|
|
17
|
+
_true_dim (int): True dimensionality of the control variables.
|
|
18
|
+
_true_slice (slice): Slice for accessing true control variables.
|
|
19
|
+
_augmented_slice (slice): Slice for accessing augmented control variables.
|
|
20
|
+
|
|
21
|
+
Notes:
|
|
22
|
+
Attributes prefixed with underscore (_) are for internal use only and should not be accessed directly.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
```python
|
|
26
|
+
control = Control("thrust", (3,))
|
|
27
|
+
control.min = [-1, -1, 0]
|
|
28
|
+
control.max = [1, 1, 10]
|
|
29
|
+
control.guess = np.repeat([[0, 0, 10]], 5, axis=0)
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, name, shape):
|
|
34
|
+
"""Initialize a Control object.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name (str): Name identifier for the control variable
|
|
38
|
+
shape (tuple): Shape of the control vector
|
|
39
|
+
"""
|
|
40
|
+
super().__init__(name, shape)
|
|
41
|
+
self._true_dim = shape[0]
|
|
42
|
+
self._update_slices()
|
|
43
|
+
|
|
44
|
+
def _update_slices(self):
|
|
45
|
+
"""Update the slice objects for true and augmented controls."""
|
|
46
|
+
self._true_slice = slice(0, self._true_dim)
|
|
47
|
+
self._augmented_slice = slice(self._true_dim, self.shape[0])
|
|
48
|
+
|
|
49
|
+
def append(self, other=None, *, min=-np.inf, max=np.inf, guess=0.0, augmented=False):
|
|
50
|
+
"""Append another control or create a new control variable.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
other (Control, optional): Another Control object to append
|
|
54
|
+
min (float, optional): Minimum bound for new control. Defaults to -np.inf
|
|
55
|
+
max (float, optional): Maximum bound for new control. Defaults to np.inf
|
|
56
|
+
guess (float, optional): Initial guess for new control. Defaults to 0.0
|
|
57
|
+
augmented (bool, optional): Whether the new control is augmented. Defaults to False
|
|
58
|
+
"""
|
|
59
|
+
if isinstance(other, Control):
|
|
60
|
+
super().append(other=other)
|
|
61
|
+
if not augmented:
|
|
62
|
+
self._true_dim += getattr(other, "_true_dim", other.shape[0])
|
|
63
|
+
self._update_slices()
|
|
64
|
+
else:
|
|
65
|
+
temp = Control(name=f"{self.name}_temp_append", shape=(1,))
|
|
66
|
+
temp.min = min
|
|
67
|
+
temp.max = max
|
|
68
|
+
temp.guess = guess
|
|
69
|
+
self.append(temp, augmented=augmented)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def true(self):
|
|
73
|
+
"""Get the true control variables (excluding augmented controls).
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Control: A new Control object containing only the true control variables
|
|
77
|
+
"""
|
|
78
|
+
return self[self._true_slice]
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def augmented(self):
|
|
82
|
+
"""Get the augmented control variables.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Control: A new Control object containing only the augmented control variables
|
|
86
|
+
"""
|
|
87
|
+
return self[self._augmented_slice]
|
|
88
|
+
|
|
89
|
+
def __getitem__(self, idx):
|
|
90
|
+
"""Get a subset of the control variables.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
idx: Index or slice to select control variables
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Control: A new Control object containing the selected variables
|
|
97
|
+
"""
|
|
98
|
+
new_ctrl = super().__getitem__(idx)
|
|
99
|
+
new_ctrl.__class__ = Control
|
|
100
|
+
|
|
101
|
+
if isinstance(idx, slice):
|
|
102
|
+
selected = np.arange(self.shape[0])[idx]
|
|
103
|
+
elif isinstance(idx, (list, np.ndarray)):
|
|
104
|
+
selected = np.array(idx)
|
|
105
|
+
else:
|
|
106
|
+
selected = np.array([idx])
|
|
107
|
+
|
|
108
|
+
new_ctrl._true_dim = np.sum(selected < self._true_dim)
|
|
109
|
+
new_ctrl._update_slices()
|
|
110
|
+
|
|
111
|
+
return new_ctrl
|
|
112
|
+
|
|
113
|
+
def __repr__(self):
|
|
114
|
+
"""String representation of the Control object.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
str: A string describing the Control object
|
|
118
|
+
"""
|
|
119
|
+
return f"Control('{self.name}', shape={self.shape})"
|
openscvx/backend/expr.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Expr:
|
|
5
|
+
"""
|
|
6
|
+
Base class for symbolic expressions in optimization problems.
|
|
7
|
+
|
|
8
|
+
Note: This class is currently not being used.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __add__(self, other):
|
|
12
|
+
return Add(self, to_expr(other))
|
|
13
|
+
|
|
14
|
+
def __mul__(self, other):
|
|
15
|
+
return Mul(self, to_expr(other))
|
|
16
|
+
|
|
17
|
+
def __matmul__(self, other):
|
|
18
|
+
return MatMul(self, to_expr(other))
|
|
19
|
+
|
|
20
|
+
def __neg__(self):
|
|
21
|
+
return Neg(self)
|
|
22
|
+
|
|
23
|
+
def children(self):
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
def pretty(self, indent=0):
|
|
27
|
+
pad = ' ' * indent
|
|
28
|
+
lines = [f"{pad}{self.__class__.__name__}"]
|
|
29
|
+
for child in self.children():
|
|
30
|
+
lines.append(child.pretty(indent + 1))
|
|
31
|
+
return '\n'.join(lines)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Add(Expr):
|
|
35
|
+
def __init__(self, left, right):
|
|
36
|
+
self.left = left
|
|
37
|
+
self.right = right
|
|
38
|
+
def children(self):
|
|
39
|
+
return [self.left, self.right]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Mul(Expr):
|
|
43
|
+
def __init__(self, left, right):
|
|
44
|
+
self.left = left
|
|
45
|
+
self.right = right
|
|
46
|
+
def children(self):
|
|
47
|
+
return [self.left, self.right]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class MatMul(Expr):
|
|
51
|
+
def __init__(self, left, right):
|
|
52
|
+
self.left = left
|
|
53
|
+
self.right = right
|
|
54
|
+
def children(self):
|
|
55
|
+
return [self.left, self.right]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Neg(Expr):
|
|
59
|
+
def __init__(self, operand):
|
|
60
|
+
self.operand = operand
|
|
61
|
+
def children(self):
|
|
62
|
+
return [self.operand]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from openscvx.backend.expr import Expr
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Parameter(Expr):
|
|
6
|
+
"""A class representing a parameter in the optimization problem.
|
|
7
|
+
|
|
8
|
+
Parameters are symbolic variables that can be used in expressions and constraints.
|
|
9
|
+
They maintain a registry of all created parameters and can be indexed or sliced.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
_registry (dict): Class-level dictionary storing all created parameters.
|
|
13
|
+
name (str): The name of the parameter.
|
|
14
|
+
_shape (tuple): The shape of the parameter.
|
|
15
|
+
value (any): The current value of the parameter, initially None.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_registry = {}
|
|
19
|
+
|
|
20
|
+
def __init__(self, name, shape=()):
|
|
21
|
+
"""Initialize a new Parameter.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
name (str): The name of the parameter.
|
|
25
|
+
shape (tuple, optional): The shape of the parameter. Defaults to ().
|
|
26
|
+
|
|
27
|
+
Note:
|
|
28
|
+
The parameter is automatically registered in the class registry if not already present.
|
|
29
|
+
"""
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.name = name
|
|
32
|
+
self._shape = shape
|
|
33
|
+
self.value = None
|
|
34
|
+
|
|
35
|
+
# Automatically register the parameter if not already present
|
|
36
|
+
if name not in Parameter._registry:
|
|
37
|
+
Parameter._registry[name] = self
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def shape(self):
|
|
41
|
+
"""Get the shape of the parameter.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
tuple: The shape of the parameter.
|
|
45
|
+
"""
|
|
46
|
+
return self._shape
|
|
47
|
+
|
|
48
|
+
def __getitem__(self, idx):
|
|
49
|
+
"""Get a subset of the parameter using indexing or slicing.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
idx (int or slice): The index or slice to use.
|
|
53
|
+
- If int: Returns a scalar parameter
|
|
54
|
+
- If slice: Returns a parameter with shape (length of slice,)
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Parameter: A new parameter representing the subset.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
TypeError: If idx is neither an int nor a slice.
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(idx, int):
|
|
63
|
+
param = Parameter(f"{self.name}[{idx}]", shape=())
|
|
64
|
+
elif isinstance(idx, slice):
|
|
65
|
+
length = len(range(*idx.indices(self.shape[0])))
|
|
66
|
+
param = Parameter(f"{self.name}[{idx.start}:{idx.stop}]", shape=(length,))
|
|
67
|
+
else:
|
|
68
|
+
raise TypeError("Parameter indices must be int or slice")
|
|
69
|
+
|
|
70
|
+
return param
|
|
71
|
+
|
|
72
|
+
def __repr__(self):
|
|
73
|
+
"""Get a string representation of the parameter.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
str: A string showing the parameter name and shape.
|
|
77
|
+
"""
|
|
78
|
+
return f"Parameter('{self.name}', shape={self.shape})"
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def get_all(cls):
|
|
82
|
+
"""Get all registered parameters.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
dict: A dictionary of all registered parameters, with names as keys.
|
|
86
|
+
"""
|
|
87
|
+
return dict(cls._registry)
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def reset(cls):
|
|
91
|
+
"""Clear the registry of all parameters.
|
|
92
|
+
|
|
93
|
+
This method removes all registered parameters from the class registry.
|
|
94
|
+
"""
|
|
95
|
+
cls._registry.clear()
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from openscvx.backend.variable import Variable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Fix:
|
|
7
|
+
"""Class representing a fixed state variable in the optimization problem.
|
|
8
|
+
|
|
9
|
+
A fixed state variable is one that is constrained to a specific value
|
|
10
|
+
and cannot be optimized.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
value: The fixed value that the state variable must take.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, value):
|
|
16
|
+
"""Initialize a new fixed state variable.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
value: The fixed value that the state variable must take.
|
|
20
|
+
"""
|
|
21
|
+
self.value = value
|
|
22
|
+
|
|
23
|
+
def __repr__(self):
|
|
24
|
+
"""Get a string representation of this fixed state variable.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
str: A string representation showing the fixed value.
|
|
28
|
+
"""
|
|
29
|
+
return f"Fix({self.value})"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Free:
|
|
33
|
+
"""Class representing a free state variable in the optimization problem.
|
|
34
|
+
|
|
35
|
+
A free state variable is one that is not constrained to any specific value
|
|
36
|
+
but can be optimized within its bounds.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
guess: The initial guess value for optimization.
|
|
40
|
+
"""
|
|
41
|
+
def __init__(self, guess):
|
|
42
|
+
"""Initialize a new free state variable.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
guess: The initial guess value for optimization.
|
|
46
|
+
"""
|
|
47
|
+
self.guess = guess
|
|
48
|
+
|
|
49
|
+
def __repr__(self):
|
|
50
|
+
"""Get a string representation of this free state variable.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: A string representation showing the guess value.
|
|
54
|
+
"""
|
|
55
|
+
return f"Free({self.guess})"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Minimize:
|
|
59
|
+
"""Class representing a state variable to be minimized in the optimization problem.
|
|
60
|
+
|
|
61
|
+
A minimized state variable is one that is optimized to achieve the lowest
|
|
62
|
+
possible value within its bounds.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
guess: The initial guess value for optimization.
|
|
66
|
+
"""
|
|
67
|
+
def __init__(self, guess):
|
|
68
|
+
"""Initialize a new minimized state variable.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
guess: The initial guess value for optimization.
|
|
72
|
+
"""
|
|
73
|
+
self.guess = guess
|
|
74
|
+
|
|
75
|
+
def __repr__(self):
|
|
76
|
+
"""Get a string representation of this minimized state variable.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
str: A string representation showing the guess value.
|
|
80
|
+
"""
|
|
81
|
+
return f"Minimize({self.guess})"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Maximize:
|
|
85
|
+
"""Class representing a state variable to be maximized in the optimization problem.
|
|
86
|
+
|
|
87
|
+
A maximized state variable is one that is optimized to achieve the highest
|
|
88
|
+
possible value within its bounds.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
guess: The initial guess value for optimization.
|
|
92
|
+
"""
|
|
93
|
+
def __init__(self, guess):
|
|
94
|
+
"""Initialize a new maximized state variable.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
guess: The initial guess value for optimization.
|
|
98
|
+
"""
|
|
99
|
+
self.guess = guess
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
"""Get a string representation of this maximized state variable.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
str: A string representation showing the guess value.
|
|
106
|
+
"""
|
|
107
|
+
return f"Maximize({self.guess})"
|
|
108
|
+
|
|
109
|
+
class State(Variable):
|
|
110
|
+
"""A class representing the state variables in an optimal control problem.
|
|
111
|
+
|
|
112
|
+
The State class extends Variable to handle state-specific properties like initial and final conditions,
|
|
113
|
+
as well as true and augmented state dimensions. It supports various boundary condition types:
|
|
114
|
+
- Fixed values (Fix)
|
|
115
|
+
- Free variables (Free)
|
|
116
|
+
- Minimization objectives (Minimize)
|
|
117
|
+
- Maximization objectives (Maximize)
|
|
118
|
+
|
|
119
|
+
Attributes:
|
|
120
|
+
name (str): Name of the state variable.
|
|
121
|
+
shape (tuple): Shape of the state variable array.
|
|
122
|
+
min (np.ndarray): Minimum bounds for the state variables. Shape: (n_states,).
|
|
123
|
+
max (np.ndarray): Maximum bounds for the state variables. Shape: (n_states,).
|
|
124
|
+
guess (np.ndarray): Used to initialize SCP and contains the current SCP solution for the state trajectory. Shape: (n_nodes, n_states).
|
|
125
|
+
initial (np.ndarray): Initial state values or boundary condition objects (Free, Fixed, Minimize, Maximize). Shape: (n_states,).
|
|
126
|
+
final (np.ndarray): Final state values or boundary condition objects (Free, Fixed, Minimize, Maximize). Shape: (n_states,).
|
|
127
|
+
_initial (np.ndarray): Internal storage for initial state values.
|
|
128
|
+
_final (np.ndarray): Internal storage for final state values.
|
|
129
|
+
initial_type (str): Type of initial boundary condition ('fix', 'free', 'minimize', 'maximize').
|
|
130
|
+
final_type (str): Type of final boundary condition ('fix', 'free', 'minimize', 'maximize').
|
|
131
|
+
_true_dim (int): True dimensionality of the state variables.
|
|
132
|
+
_true_slice (slice): Slice for accessing true state variables.
|
|
133
|
+
_augmented_slice (slice): Slice for accessing augmented state variables.
|
|
134
|
+
|
|
135
|
+
Notes:
|
|
136
|
+
Attributes prefixed with underscore (_) are for internal use only and should not be accessed directly.
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
```python
|
|
140
|
+
state = State("position", (3,))
|
|
141
|
+
state.min = np.array([0, 0, 10])
|
|
142
|
+
state.max = np.array([10, 10, 200])
|
|
143
|
+
state.guess = np.linspace([0, 1, 2], [10, 5, 8], 3)
|
|
144
|
+
state.initial = np.array([Fix(0), Free(1), 2])
|
|
145
|
+
state.final = np.array([Fix(10), Free(5), Maximize(8)])
|
|
146
|
+
```
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, name, shape):
|
|
150
|
+
"""Initialize a State object.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
name (str): Name identifier for the state variable
|
|
154
|
+
shape (tuple): Shape of the state vector
|
|
155
|
+
"""
|
|
156
|
+
super().__init__(name, shape)
|
|
157
|
+
self._initial = None
|
|
158
|
+
self.initial_type = None
|
|
159
|
+
self._final = None
|
|
160
|
+
self.final_type = None
|
|
161
|
+
|
|
162
|
+
self._true_dim = shape[0]
|
|
163
|
+
self._update_slices()
|
|
164
|
+
|
|
165
|
+
def _update_slices(self):
|
|
166
|
+
"""Update the slice objects for true and augmented states."""
|
|
167
|
+
self._true_slice = slice(0, self._true_dim)
|
|
168
|
+
self._augmented_slice = slice(self._true_dim, self.shape[0])
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def min(self):
|
|
172
|
+
"""Get the minimum bounds for the state variables.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
np.ndarray: Array of minimum values for each state variable
|
|
176
|
+
"""
|
|
177
|
+
return self._min
|
|
178
|
+
|
|
179
|
+
@min.setter
|
|
180
|
+
def min(self, val):
|
|
181
|
+
"""Set the minimum bounds for the state variables.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
val (np.ndarray): Array of minimum values for each state variable
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
ValueError: If the shape of val doesn't match the state shape
|
|
188
|
+
"""
|
|
189
|
+
val = np.asarray(val)
|
|
190
|
+
if val.shape != self.shape:
|
|
191
|
+
raise ValueError(f"Min shape {val.shape} does not match State shape {self.shape}")
|
|
192
|
+
self._min = val
|
|
193
|
+
self._check_bounds_against_initial_final()
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def max(self):
|
|
197
|
+
"""Get the maximum bounds for the state variables.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
np.ndarray: Array of maximum values for each state variable
|
|
201
|
+
"""
|
|
202
|
+
return self._max
|
|
203
|
+
|
|
204
|
+
@max.setter
|
|
205
|
+
def max(self, val):
|
|
206
|
+
"""Set the maximum bounds for the state variables.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
val (np.ndarray): Array of maximum values for each state variable
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: If the shape of val doesn't match the state shape
|
|
213
|
+
"""
|
|
214
|
+
val = np.asarray(val)
|
|
215
|
+
if val.shape != self.shape:
|
|
216
|
+
raise ValueError(f"Max shape {val.shape} does not match State shape {self.shape}")
|
|
217
|
+
self._max = val
|
|
218
|
+
self._check_bounds_against_initial_final()
|
|
219
|
+
|
|
220
|
+
def _check_bounds_against_initial_final(self):
|
|
221
|
+
"""Check if initial and final values respect the bounds.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
ValueError: If any fixed initial or final value violates the bounds
|
|
225
|
+
"""
|
|
226
|
+
for field_name, data, types in [('initial', self._initial, self.initial_type),
|
|
227
|
+
('final', self._final, self.final_type)]:
|
|
228
|
+
if data is None or types is None:
|
|
229
|
+
continue
|
|
230
|
+
for i, val in np.ndenumerate(data):
|
|
231
|
+
if types[i] != "Fix":
|
|
232
|
+
continue
|
|
233
|
+
min_i = self._min[i] if self._min is not None else -np.inf
|
|
234
|
+
max_i = self._max[i] if self._max is not None else np.inf
|
|
235
|
+
if val < min_i:
|
|
236
|
+
raise ValueError(f"{field_name.capitalize()} Fixed value at index {i[0]} is lower then the min: {val} < {min_i}")
|
|
237
|
+
if val > max_i:
|
|
238
|
+
raise ValueError(f"{field_name.capitalize()} Fixed value at index {i[0]} is greater then the max: {val} > {max_i}")
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def initial(self):
|
|
242
|
+
"""Get the initial state values.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
np.ndarray: Array of initial state values
|
|
246
|
+
"""
|
|
247
|
+
return self._initial
|
|
248
|
+
|
|
249
|
+
@initial.setter
|
|
250
|
+
def initial(self, arr):
|
|
251
|
+
"""Set the initial state values and their types.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
arr (np.ndarray): Array of initial values or boundary condition objects
|
|
255
|
+
(Fix, Free, Minimize, Maximize)
|
|
256
|
+
|
|
257
|
+
Raises:
|
|
258
|
+
ValueError: If the shape of arr doesn't match the state shape
|
|
259
|
+
"""
|
|
260
|
+
arr = np.asarray(arr, dtype=object)
|
|
261
|
+
if arr.shape != self.shape:
|
|
262
|
+
raise ValueError(f"Initial value shape {arr.shape} does not match State shape {self.shape}")
|
|
263
|
+
self._initial = np.zeros(arr.shape)
|
|
264
|
+
self.initial_type = np.full(arr.shape, "Fix", dtype=object)
|
|
265
|
+
|
|
266
|
+
for i, v in np.ndenumerate(arr):
|
|
267
|
+
if isinstance(v, Free):
|
|
268
|
+
self._initial[i] = v.guess
|
|
269
|
+
self.initial_type[i] = "Free"
|
|
270
|
+
elif isinstance(v, Minimize):
|
|
271
|
+
self._initial[i] = v.guess
|
|
272
|
+
self.initial_type[i] = "Minimize"
|
|
273
|
+
elif isinstance(v, Maximize):
|
|
274
|
+
self._initial[i] = v.guess
|
|
275
|
+
self.initial_type[i] = "Maximize"
|
|
276
|
+
elif isinstance(v, Fix):
|
|
277
|
+
val = v.value
|
|
278
|
+
self._initial[i] = val
|
|
279
|
+
self.initial_type[i] = "Fix"
|
|
280
|
+
else:
|
|
281
|
+
val = v
|
|
282
|
+
self._initial[i] = val
|
|
283
|
+
self.initial_type[i] = "Fix"
|
|
284
|
+
|
|
285
|
+
self._check_bounds_against_initial_final()
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def final(self):
|
|
289
|
+
"""Get the final state values.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
np.ndarray: Array of final state values
|
|
293
|
+
"""
|
|
294
|
+
return self._final
|
|
295
|
+
|
|
296
|
+
@final.setter
|
|
297
|
+
def final(self, arr):
|
|
298
|
+
"""Set the final state values and their types.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
arr (np.ndarray): Array of final values or boundary condition objects
|
|
302
|
+
(Fix, Free, Minimize, Maximize)
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
ValueError: If the shape of arr doesn't match the state shape
|
|
306
|
+
"""
|
|
307
|
+
arr = np.asarray(arr, dtype=object)
|
|
308
|
+
if arr.shape != self.shape:
|
|
309
|
+
raise ValueError(f"Final value shape {arr.shape} does not match State shape {self.shape}")
|
|
310
|
+
self._final = np.zeros(arr.shape)
|
|
311
|
+
self.final_type = np.full(arr.shape, "Fix", dtype=object)
|
|
312
|
+
|
|
313
|
+
for i, v in np.ndenumerate(arr):
|
|
314
|
+
if isinstance(v, Free):
|
|
315
|
+
self._final[i] = v.guess
|
|
316
|
+
self.final_type[i] = "Free"
|
|
317
|
+
elif isinstance(v, Minimize):
|
|
318
|
+
self._final[i] = v.guess
|
|
319
|
+
self.final_type[i] = "Minimize"
|
|
320
|
+
elif isinstance(v, Maximize):
|
|
321
|
+
self._final[i] = v.guess
|
|
322
|
+
self.final_type[i] = "Maximize"
|
|
323
|
+
elif isinstance(v, Fix):
|
|
324
|
+
val = v.value
|
|
325
|
+
self._final[i] = val
|
|
326
|
+
self.final_type[i] = "Fix"
|
|
327
|
+
else:
|
|
328
|
+
val = v
|
|
329
|
+
self._final[i] = val
|
|
330
|
+
self.final_type[i] = "Fix"
|
|
331
|
+
|
|
332
|
+
self._check_bounds_against_initial_final()
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def true(self):
|
|
336
|
+
"""Get the true state variables (excluding augmented states).
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
State: A new State object containing only the true state variables
|
|
340
|
+
"""
|
|
341
|
+
return self[self._true_slice]
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def augmented(self):
|
|
345
|
+
"""Get the augmented state variables.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
State: A new State object containing only the augmented state variables
|
|
349
|
+
"""
|
|
350
|
+
return self[self._augmented_slice]
|
|
351
|
+
|
|
352
|
+
def append(self, other=None, *, min=-np.inf, max=np.inf, guess=0.0, initial=0.0, final=0.0, augmented=False):
|
|
353
|
+
"""Append another state or create a new state variable.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
other (State, optional): Another State object to append
|
|
357
|
+
min (float, optional): Minimum bound for new state. Defaults to -np.inf
|
|
358
|
+
max (float, optional): Maximum bound for new state. Defaults to np.inf
|
|
359
|
+
guess (float, optional): Initial guess for new state. Defaults to 0.0
|
|
360
|
+
initial (float, optional): Initial value for new state. Defaults to 0.0
|
|
361
|
+
final (float, optional): Final value for new state. Defaults to 0.0
|
|
362
|
+
augmented (bool, optional): Whether the new state is augmented. Defaults to False
|
|
363
|
+
"""
|
|
364
|
+
if isinstance(other, State):
|
|
365
|
+
super().append(other=other)
|
|
366
|
+
|
|
367
|
+
if self._initial is None:
|
|
368
|
+
self._initial = np.array(other._initial) if other._initial is not None else None
|
|
369
|
+
elif other._initial is not None:
|
|
370
|
+
self._initial = np.concatenate([self._initial, other._initial], axis=0)
|
|
371
|
+
|
|
372
|
+
if self._final is None:
|
|
373
|
+
self._final = np.array(other._final) if other._final is not None else None
|
|
374
|
+
elif other._final is not None:
|
|
375
|
+
self._final = np.concatenate([self._final, other._final], axis=0)
|
|
376
|
+
|
|
377
|
+
if self.initial_type is None:
|
|
378
|
+
self.initial_type = np.array(other.initial_type) if other.initial_type is not None else None
|
|
379
|
+
elif other.initial_type is not None:
|
|
380
|
+
self.initial_type = np.concatenate([self.initial_type, other.initial_type], axis=0)
|
|
381
|
+
|
|
382
|
+
if self.final_type is None:
|
|
383
|
+
self.final_type = np.array(other.final_type) if other.final_type is not None else None
|
|
384
|
+
elif other.final_type is not None:
|
|
385
|
+
self.final_type = np.concatenate([self.final_type, other.final_type], axis=0)
|
|
386
|
+
|
|
387
|
+
if not augmented:
|
|
388
|
+
self._true_dim += getattr(other, "_true_dim", other.shape[0])
|
|
389
|
+
self._update_slices()
|
|
390
|
+
else:
|
|
391
|
+
temp_state = State(name=f"{self.name}_temp_append", shape=(1,))
|
|
392
|
+
temp_state.min = min
|
|
393
|
+
temp_state.max = max
|
|
394
|
+
temp_state.guess = guess
|
|
395
|
+
temp_state.initial = initial
|
|
396
|
+
temp_state.final = final
|
|
397
|
+
self.append(temp_state, augmented=augmented)
|
|
398
|
+
|
|
399
|
+
def __getitem__(self, idx):
|
|
400
|
+
"""Get a subset of the state variables.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
idx: Index or slice to select state variables
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
State: A new State object containing the selected variables
|
|
407
|
+
"""
|
|
408
|
+
new_state = super().__getitem__(idx)
|
|
409
|
+
new_state.__class__ = State
|
|
410
|
+
|
|
411
|
+
def slice_attr(attr):
|
|
412
|
+
if attr is None:
|
|
413
|
+
return None
|
|
414
|
+
if attr.ndim == 2 and attr.shape[1] == self.shape[0]:
|
|
415
|
+
return attr[:, idx]
|
|
416
|
+
return attr[idx]
|
|
417
|
+
|
|
418
|
+
new_state._initial = slice_attr(self._initial)
|
|
419
|
+
new_state.initial_type = slice_attr(self.initial_type)
|
|
420
|
+
new_state._final = slice_attr(self._final)
|
|
421
|
+
new_state.final_type = slice_attr(self.final_type)
|
|
422
|
+
|
|
423
|
+
if isinstance(idx, slice):
|
|
424
|
+
selected = np.arange(self.shape[0])[idx]
|
|
425
|
+
elif isinstance(idx, (list, np.ndarray)):
|
|
426
|
+
selected = np.array(idx)
|
|
427
|
+
else:
|
|
428
|
+
selected = np.array([idx])
|
|
429
|
+
|
|
430
|
+
new_state._true_dim = np.sum(selected < self._true_dim)
|
|
431
|
+
new_state._update_slices()
|
|
432
|
+
|
|
433
|
+
return new_state
|
|
434
|
+
|
|
435
|
+
def __repr__(self):
|
|
436
|
+
"""String representation of the State object.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
str: A string describing the State object
|
|
440
|
+
"""
|
|
441
|
+
return f"State('{self.name}', shape={self.shape})"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# utils.py
|
|
2
|
+
|
|
3
|
+
from expr import Const, Add, Mul, MatMul, Neg
|
|
4
|
+
|
|
5
|
+
def pretty_print(expr):
|
|
6
|
+
print(expr.pretty())
|
|
7
|
+
|
|
8
|
+
def evaluate(expr, values):
|
|
9
|
+
if hasattr(expr, 'name'):
|
|
10
|
+
return values[expr.name]
|
|
11
|
+
|
|
12
|
+
if isinstance(expr, Const):
|
|
13
|
+
return expr.value
|
|
14
|
+
|
|
15
|
+
if isinstance(expr, Add):
|
|
16
|
+
return evaluate(expr.a, values) + evaluate(expr.b, values)
|
|
17
|
+
|
|
18
|
+
if isinstance(expr, Mul):
|
|
19
|
+
return evaluate(expr.a, values) * evaluate(expr.b, values)
|
|
20
|
+
|
|
21
|
+
if isinstance(expr, MatMul):
|
|
22
|
+
return evaluate(expr.a, values) @ evaluate(expr.b, values)
|
|
23
|
+
|
|
24
|
+
if isinstance(expr, Neg):
|
|
25
|
+
return -evaluate(expr.a, values)
|
|
26
|
+
|
|
27
|
+
raise NotImplementedError(f"Evaluation not implemented for {type(expr)}")
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from openscvx.backend.expr import Expr
|
|
4
|
+
|
|
5
|
+
class Variable(Expr):
|
|
6
|
+
"""A base class for variables in an optimal control problem.
|
|
7
|
+
|
|
8
|
+
The Variable class provides the fundamental structure for state and control variables,
|
|
9
|
+
handling their shapes, bounds, and initial guesses. It supports operations like
|
|
10
|
+
appending new variables and slicing.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
name (str): Name identifier for the variable
|
|
14
|
+
_shape (tuple): Shape of the variable vector
|
|
15
|
+
_min (np.ndarray): Minimum bounds for the variable
|
|
16
|
+
_max (np.ndarray): Maximum bounds for the variable
|
|
17
|
+
_guess (np.ndarray): Initial guess for the variable trajectory
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, name, shape):
|
|
22
|
+
"""Initialize a Variable object.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name (str): Name identifier for the variable
|
|
26
|
+
shape (tuple): Shape of the variable vector
|
|
27
|
+
"""
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.name = name
|
|
30
|
+
self._shape = shape
|
|
31
|
+
self._min = None
|
|
32
|
+
self._max = None
|
|
33
|
+
self._guess = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def shape(self):
|
|
37
|
+
"""Get the shape of the variable.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
tuple: Shape of the variable vector
|
|
41
|
+
"""
|
|
42
|
+
return self._shape
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def min(self):
|
|
46
|
+
"""Get the minimum bounds for the variable.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
np.ndarray: Array of minimum values for each variable
|
|
50
|
+
"""
|
|
51
|
+
return self._min
|
|
52
|
+
|
|
53
|
+
@min.setter
|
|
54
|
+
def min(self, arr):
|
|
55
|
+
"""Set the minimum bounds for the variable.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
arr (np.ndarray): Array of minimum values for each variable
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ValueError: If the shape of arr doesn't match the variable shape
|
|
62
|
+
"""
|
|
63
|
+
arr = np.asarray(arr, dtype=float)
|
|
64
|
+
if arr.ndim != 1 or arr.shape[0] != self.shape[0]:
|
|
65
|
+
raise ValueError(f"{self.__class__.__name__} min must be 1D with shape ({self.shape[0]},), got {arr.shape}")
|
|
66
|
+
self._min = arr
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def max(self):
|
|
70
|
+
"""Get the maximum bounds for the variable.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
np.ndarray: Array of maximum values for each variable
|
|
74
|
+
"""
|
|
75
|
+
return self._max
|
|
76
|
+
|
|
77
|
+
@max.setter
|
|
78
|
+
def max(self, arr):
|
|
79
|
+
"""Set the maximum bounds for the variable.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
arr (np.ndarray): Array of maximum values for each variable
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ValueError: If the shape of arr doesn't match the variable shape
|
|
86
|
+
"""
|
|
87
|
+
arr = np.asarray(arr, dtype=float)
|
|
88
|
+
if arr.ndim != 1 or arr.shape[0] != self.shape[0]:
|
|
89
|
+
raise ValueError(f"{self.__class__.__name__} max must be 1D with shape ({self.shape[0]},), got {arr.shape}")
|
|
90
|
+
self._max = arr
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def guess(self):
|
|
94
|
+
"""Get the initial guess for the variable trajectory.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
np.ndarray: Array of initial guesses for each variable at each time point
|
|
98
|
+
"""
|
|
99
|
+
return self._guess
|
|
100
|
+
|
|
101
|
+
@guess.setter
|
|
102
|
+
def guess(self, arr):
|
|
103
|
+
"""Set the initial guess for the variable trajectory.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
arr (np.ndarray): 2D array of initial guesses with shape (n_guess_points, n_variables)
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
ValueError: If the shape of arr doesn't match the expected dimensions
|
|
110
|
+
"""
|
|
111
|
+
arr = np.asarray(arr)
|
|
112
|
+
if arr.ndim != 2:
|
|
113
|
+
raise ValueError(f"Guess must be a 2D array of shape (n_guess_points, {self.shape[0]}), got shape {arr.shape}")
|
|
114
|
+
if arr.shape[1] != self.shape[0]:
|
|
115
|
+
raise ValueError(f"Guess must have second dimension equal to variable dimension {self.shape[0]}, got {arr.shape[1]}")
|
|
116
|
+
self._guess = arr
|
|
117
|
+
|
|
118
|
+
def append(self, other=None, *, min=-np.inf, max=np.inf, guess=0.0):
|
|
119
|
+
"""Append another variable or create a new variable.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
other (Variable, optional): Another Variable object to append
|
|
123
|
+
min (float, optional): Minimum bound for new variable. Defaults to -np.inf
|
|
124
|
+
max (float, optional): Maximum bound for new variable. Defaults to np.inf
|
|
125
|
+
guess (float, optional): Initial guess for new variable. Defaults to 0.0
|
|
126
|
+
"""
|
|
127
|
+
def process_array(val, is_guess=False):
|
|
128
|
+
"""Process input array to ensure correct shape and type.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
val: Input value to process
|
|
132
|
+
is_guess (bool): Whether the value is a guess array
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
np.ndarray: Processed array with correct shape and type
|
|
136
|
+
"""
|
|
137
|
+
arr = np.asarray(val, dtype=float)
|
|
138
|
+
if is_guess:
|
|
139
|
+
return np.atleast_2d(arr)
|
|
140
|
+
return np.atleast_1d(arr)
|
|
141
|
+
|
|
142
|
+
if isinstance(other, Variable):
|
|
143
|
+
self._shape = (self.shape[0] + other.shape[0],)
|
|
144
|
+
|
|
145
|
+
if self._min is not None and other._min is not None:
|
|
146
|
+
self._min = np.concatenate([self._min, process_array(other._min)], axis=0)
|
|
147
|
+
|
|
148
|
+
if self._max is not None and other._max is not None:
|
|
149
|
+
self._max = np.concatenate([self._max, process_array(other._max)], axis=0)
|
|
150
|
+
|
|
151
|
+
if self._guess is not None and other._guess is not None:
|
|
152
|
+
self._guess = np.concatenate([self._guess, process_array(other._guess, is_guess=True)], axis=1)
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
self._shape = (self.shape[0] + 1,)
|
|
156
|
+
|
|
157
|
+
if self._min is not None:
|
|
158
|
+
self._min = np.concatenate([self._min, process_array(min)], axis=0)
|
|
159
|
+
|
|
160
|
+
if self._max is not None:
|
|
161
|
+
self._max = np.concatenate([self._max, process_array(max)], axis=0)
|
|
162
|
+
|
|
163
|
+
if self._guess is not None:
|
|
164
|
+
guess_arr = process_array(guess, is_guess=True)
|
|
165
|
+
if guess_arr.shape[1] != 1:
|
|
166
|
+
guess_arr = guess_arr.T
|
|
167
|
+
self._guess = np.concatenate([self._guess, guess_arr], axis=1)
|
|
168
|
+
|
|
169
|
+
def __getitem__(self, idx):
|
|
170
|
+
"""Get a subset of the variable.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
idx (int or slice): Index or slice to select variables
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Variable: A new Variable object containing the selected variables
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
TypeError: If idx is not an int or slice
|
|
180
|
+
"""
|
|
181
|
+
if isinstance(idx, int):
|
|
182
|
+
new_shape = ()
|
|
183
|
+
elif isinstance(idx, slice):
|
|
184
|
+
new_shape = (len(range(*idx.indices(self.shape[0]))),)
|
|
185
|
+
else:
|
|
186
|
+
raise TypeError("Variable indices must be int or slice")
|
|
187
|
+
|
|
188
|
+
sliced = Variable(f"{self.name}[{idx}]", new_shape)
|
|
189
|
+
|
|
190
|
+
def slice_attr(attr):
|
|
191
|
+
"""Slice an attribute array based on the index.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
attr (np.ndarray): Attribute array to slice
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
np.ndarray: Sliced attribute array
|
|
198
|
+
"""
|
|
199
|
+
if attr is None:
|
|
200
|
+
return None
|
|
201
|
+
if attr.ndim == 2 and attr.shape[1] == self.shape[0]:
|
|
202
|
+
return attr[:, idx]
|
|
203
|
+
return attr[idx]
|
|
204
|
+
|
|
205
|
+
sliced._min = slice_attr(self._min)
|
|
206
|
+
sliced._max = slice_attr(self._max)
|
|
207
|
+
sliced._guess = slice_attr(self._guess)
|
|
208
|
+
|
|
209
|
+
return sliced
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
openscvx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
openscvx/_version.py,sha256=
|
|
2
|
+
openscvx/_version.py,sha256=OjGGK5TcHVG44Y62aAqeJH4CskkZoY9ydbHOtCDew50,511
|
|
3
3
|
openscvx/config.py,sha256=BsMAZUWCBUMl43nvakQ2OOdf2iszsFv9VP27J4xDXEs,16106
|
|
4
4
|
openscvx/discretization.py,sha256=FBpJKoVTXYAxeE6PvaekbRFbLWeKY6EJfl214ib2tX8,7908
|
|
5
5
|
openscvx/dynamics.py,sha256=TQosmwDXpWvR22-ZL2JawDDjVkyoL-DN45Xuv7ecCnc,6417
|
|
@@ -16,12 +16,19 @@ openscvx/utils.py,sha256=CEd_kjh-MUhRInZjoxogUjMddU5ExJ-IAOXq_Hn4KGY,4166
|
|
|
16
16
|
openscvx/augmentation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
openscvx/augmentation/ctcs.py,sha256=m1jdALXSqHq3WD6lCBAUI7FR0Sfs8aCYr66h0EwE4z4,1707
|
|
18
18
|
openscvx/augmentation/dynamics_augmentation.py,sha256=4lEeSRl3LJxvcclwGSI1Yp21kc_oKoIVga08uzaKXE0,4702
|
|
19
|
+
openscvx/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
openscvx/backend/control.py,sha256=vOAZNzVZy6-sft5OzP6ZavQiaaDTLSJhjVJHvgPH9Vo,4457
|
|
21
|
+
openscvx/backend/expr.py,sha256=GQfnQxizogUONQ4oBtSQZMlFQYesunBbT95n1EMAPRo,1374
|
|
22
|
+
openscvx/backend/parameter.py,sha256=WmH_MiruQ8rSdf_mS0RfRYJLVgIRU-nlA794GMrbOLU,3047
|
|
23
|
+
openscvx/backend/state.py,sha256=UbyerDHm_BTE1T5vI1HFYvBXxDwBl-ocdY2knJrJOGA,15952
|
|
24
|
+
openscvx/backend/utils.py,sha256=pxh-YG1pSkcxzaPe9Lx8bkmwcd-RSCs0mdaaZNMg4CE,707
|
|
25
|
+
openscvx/backend/variable.py,sha256=AqyLc13PKMm_eK1SDnqns6Fkx7hQdG--r5dnEBUfMf8,7104
|
|
19
26
|
openscvx/constraints/__init__.py,sha256=J3_UyJMUVYzvhjfyBM_m9WIRDlHQV7Q4CiI-TzRapAs,165
|
|
20
27
|
openscvx/constraints/ctcs.py,sha256=-VJZE8O0xq8n9mzSiDNikax-xo6CpWJtjwMbAPzu1-8,10121
|
|
21
28
|
openscvx/constraints/nodal.py,sha256=qT_U8Go0n0rzv4MupwQYGL8H5P8ShA7ZGATJ7IHnIqU,8185
|
|
22
29
|
openscvx/constraints/violation.py,sha256=curg7R6ER4YtKzdqmd1tgS2kWICGon5ab_Y4YGbeibw,2875
|
|
23
|
-
openscvx-0.2.
|
|
24
|
-
openscvx-0.2.
|
|
25
|
-
openscvx-0.2.
|
|
26
|
-
openscvx-0.2.
|
|
27
|
-
openscvx-0.2.
|
|
30
|
+
openscvx-0.2.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
31
|
+
openscvx-0.2.2.dist-info/METADATA,sha256=EBYY6LZeEvcvaId2WQEne9tNtnSk0IyMszpXK3-2vfE,9052
|
|
32
|
+
openscvx-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
33
|
+
openscvx-0.2.2.dist-info/top_level.txt,sha256=nUT4Ybefzh40H8tVXqc1RzKESy_MAowElb-CIvAbd4Q,9
|
|
34
|
+
openscvx-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|