Skip to content

Commit 9858a68

Browse files
committed
battery inherits now from battery_n_capacitors
1 parent 9279726 commit 9858a68

File tree

5 files changed

+173
-196
lines changed

5 files changed

+173
-196
lines changed

pySDC/implementations/problem_classes/Battery.py

Lines changed: 165 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
from pySDC.implementations.datatype_classes.mesh import mesh, imex_mesh
66

77

8-
class battery(ptype):
8+
class battery_n_capacitors(ptype):
99
"""
10-
Example implementing the battery drain model as in the description in the PinTSimE project
11-
10+
Example implementing the battery drain model with N capacitors, where N is an arbitrary integer greater than 0.
1211
Attributes:
13-
A: system matrix, representing the 2 ODEs
14-
t_switch: time point of the switch
1512
nswitches: number of switches
1613
"""
1714

@@ -32,20 +29,180 @@ def __init__(self, problem_params, dtype_u=mesh, dtype_f=imex_mesh):
3229
msg = 'need %s to instantiate problem, only got %s' % (key, str(problem_params.keys()))
3330
raise ParameterError(msg)
3431

35-
problem_params['nvars'] = problem_params['ncapacitors'] + 1
32+
n = problem_params['ncapacitors']
33+
problem_params['nvars'] = n + 1
3634

3735
# invoke super init, passing number of dofs, dtype_u and dtype_f
38-
super(battery, self).__init__(
36+
super(battery_n_capacitors, self).__init__(
3937
init=(problem_params['nvars'], None, np.dtype('float64')),
4038
dtype_u=dtype_u,
4139
dtype_f=dtype_f,
4240
params=problem_params,
4341
)
4442

45-
self.A = np.zeros((2, 2))
43+
self.A = np.zeros((n + 1, n + 1))
44+
self.switch_A, self.switch_f = self.get_problem_dict()
4645
self.t_switch = None
4746
self.nswitches = 0
4847

48+
def eval_f(self, u, t):
49+
"""
50+
Routine to evaluate the RHS. No Switch Estimator is used: For N = 3 there are N + 1 = 4 different states of the battery:
51+
1. u[1] > V_ref[0] and u[2] > V_ref[1] and u[3] > V_ref[2] -> C1 supplies energy
52+
2. u[1] <= V_ref[0] and u[2] > V_ref[1] and u[3] > V_ref[2] -> C2 supplies energy
53+
3. u[1] <= V_ref[0] and u[2] <= V_ref[1] and u[3] > V_ref[2] -> C3 supplies energy
54+
4. u[1] <= V_ref[0] and u[2] <= V_ref[1] and u[3] <= V_ref[2] -> Vs supplies energy
55+
max_index is initialized to -1. List "switch" contains a True if u[k] <= V_ref[k-1] is satisfied.
56+
- Is no True there (i.e. max_index = -1), we are in the first case.
57+
- max_index = k >= 0 means we are in the (k+1)-th case.
58+
So, the actual RHS has key max_index-1 in the dictionary self.switch_f.
59+
In case of using the Switch Estimator, we count the number of switches which illustrates in which case of voltage source we are.
60+
61+
Args:
62+
u (dtype_u): current values
63+
t (float): current time
64+
65+
Returns:
66+
dtype_f: the RHS
67+
"""
68+
69+
f = self.dtype_f(self.init, val=0.0)
70+
f.impl[:] = self.A.dot(u)
71+
72+
if self.t_switch is not None:
73+
f.expl[:] = self.switch_f[self.nswitches]
74+
75+
else:
76+
# proof all switching conditions and find largest index where it drops below V_ref
77+
switch = [True if u[k] <= self.params.V_ref[k - 1] else False for k in range(1, len(u))]
78+
max_index = max([k if switch[k] == True else -1 for k in range(len(switch))])
79+
80+
if max_index == -1:
81+
f.expl[:] = self.switch_f[0]
82+
83+
else:
84+
f.expl[:] = self.switch_f[max_index + 1]
85+
86+
return f
87+
88+
def solve_system(self, rhs, factor, u0, t):
89+
"""
90+
Simple linear solver for (I-factor*A)u = rhs
91+
92+
Args:
93+
rhs (dtype_f): right-hand side for the linear system
94+
factor (float): abbrev. for the local stepsize (or any other factor required)
95+
u0 (dtype_u): initial guess for the iterative solver
96+
t (float): current time (e.g. for time-dependent BCs)
97+
98+
Returns:
99+
dtype_u: solution as mesh
100+
"""
101+
102+
if self.t_switch is not None:
103+
self.A = self.switch_A[self.nswitches]
104+
105+
else:
106+
# proof all switching conditions and find largest index where it drops below V_ref
107+
switch = [True if rhs[k] <= self.params.V_ref[k - 1] else False for k in range(1, len(rhs))]
108+
max_index = max([k if switch[k] == True else -1 for k in range(len(switch))])
109+
if max_index == -1:
110+
self.A = self.switch_A[0]
111+
112+
else:
113+
self.A = self.switch_A[max_index + 1]
114+
115+
me = self.dtype_u(self.init)
116+
me[:] = np.linalg.solve(np.eye(self.params.nvars) - factor * self.A, rhs)
117+
return me
118+
119+
def u_exact(self, t):
120+
"""
121+
Routine to compute the exact solution at time t
122+
123+
Args:
124+
t (float): current time
125+
126+
Returns:
127+
dtype_u: exact solution
128+
"""
129+
assert t == 0, 'ERROR: u_exact only valid for t=0'
130+
131+
me = self.dtype_u(self.init)
132+
133+
me[0] = 0.0 # cL
134+
me[1:] = self.params.alpha * self.params.V_ref # vC's
135+
return me
136+
137+
def get_switching_info(self, u, t):
138+
"""
139+
Provides information about a discrete event for one subinterval.
140+
141+
Args:
142+
u (dtype_u): current values
143+
t (float): current time
144+
145+
Returns:
146+
switch_detected (bool): Indicates if a switch is found or not
147+
m_guess (np.int): Index of collocation node inside one subinterval of where the discrete event was found
148+
vC_switch (list): Contains function values of switching condition (for interpolation)
149+
"""
150+
151+
switch_detected = False
152+
m_guess = -100
153+
break_flag = False
154+
155+
for m in range(len(u)):
156+
for k in range(1, self.params.nvars):
157+
if u[m][k] - self.params.V_ref[k - 1] <= 0:
158+
switch_detected = True
159+
m_guess = m - 1
160+
k_detected = k
161+
break_flag = True
162+
break
163+
164+
if break_flag:
165+
break
166+
167+
vC_switch = (
168+
[u[m][k_detected] - self.params.V_ref[k_detected - 1] for m in range(1, len(u))] if switch_detected else []
169+
)
170+
171+
return switch_detected, m_guess, vC_switch
172+
173+
def count_switches(self):
174+
"""
175+
Counts the number of switches. This function is called when a switch is found inside the range of tolerance
176+
(in switch_estimator.py)
177+
"""
178+
179+
self.nswitches += 1
180+
181+
def get_problem_dict(self):
182+
"""
183+
Helper to create dictionaries for both the coefficent matrix of the ODE system and the nonhomogeneous part.
184+
"""
185+
186+
n = self.params.ncapacitors
187+
v = np.zeros(n + 1)
188+
v[0] = 1
189+
190+
A, f = dict(), dict()
191+
A = {k: np.diag(-1 / (self.params.C[k] * self.params.R) * np.roll(v, k + 1)) for k in range(n)}
192+
A.update({n: np.diag(-(self.params.Rs + self.params.R) / self.params.L * v)})
193+
f = {k: np.zeros(n + 1) for k in range(n)}
194+
f.update({n: self.params.Vs / self.params.L * v})
195+
return A, f
196+
197+
198+
class battery(battery_n_capacitors):
199+
"""
200+
Example implementing the battery drain model with one capacitor, inherits from battery_n_capacitors.
201+
"""
202+
203+
def __init__(self, problem_params, dtype_u=mesh, dtype_f=imex_mesh):
204+
super(battery, self).__init__(problem_params, dtype_u=dtype_u, dtype_f=dtype_f)
205+
49206
def eval_f(self, u, t):
50207
"""
51208
Routine to evaluate the RHS
@@ -269,183 +426,3 @@ def solve_system(self, rhs, factor, u0, t):
269426
me[:] = u[:]
270427

271428
return me
272-
273-
274-
class battery_n_capacitors(ptype):
275-
"""
276-
Example implementing the battery drain model with N capacitors, where N is an arbitrary integer greater than 0.
277-
Attributes:
278-
nswitches: number of switches
279-
"""
280-
281-
def __init__(self, problem_params, dtype_u=mesh, dtype_f=imex_mesh):
282-
"""
283-
Initialization routine
284-
285-
Args:
286-
problem_params (dict): custom parameters for the example
287-
dtype_u: mesh data type for solution
288-
dtype_f: mesh data type for RHS
289-
"""
290-
291-
# these parameters will be used later, so assert their existence
292-
essential_keys = ['ncapacitors', 'Vs', 'Rs', 'C', 'R', 'L', 'alpha', 'V_ref']
293-
for key in essential_keys:
294-
if key not in problem_params:
295-
msg = 'need %s to instantiate problem, only got %s' % (key, str(problem_params.keys()))
296-
raise ParameterError(msg)
297-
298-
n = problem_params['ncapacitors']
299-
problem_params['nvars'] = n + 1
300-
301-
# invoke super init, passing number of dofs, dtype_u and dtype_f
302-
super(battery_n_capacitors, self).__init__(
303-
init=(problem_params['nvars'], None, np.dtype('float64')),
304-
dtype_u=dtype_u,
305-
dtype_f=dtype_f,
306-
params=problem_params,
307-
)
308-
309-
v = np.zeros(n + 1)
310-
v[0] = 1
311-
312-
self.A = np.zeros((n + 1, n + 1))
313-
self.switch_A = {k: np.diag(-1 / (self.params.C[k] * self.params.R) * np.roll(v, k + 1)) for k in range(n)}
314-
self.switch_A.update({n: np.diag(-(self.params.Rs + self.params.R) / self.params.L * v)})
315-
self.switch_f = {k: np.zeros(n + 1) for k in range(n)}
316-
self.switch_f.update({n: self.params.Vs / self.params.L * v})
317-
self.t_switch = None
318-
self.nswitches = 0
319-
320-
def eval_f(self, u, t):
321-
"""
322-
Routine to evaluate the RHS. No Switch Estimator is used: For N = 3 there are N + 1 = 4 different states of the battery:
323-
1. u[1] > V_ref[0] and u[2] > V_ref[1] and u[3] > V_ref[2] -> C1 supplies energy
324-
2. u[1] <= V_ref[0] and u[2] > V_ref[1] and u[3] > V_ref[2] -> C2 supplies energy
325-
3. u[1] <= V_ref[0] and u[2] <= V_ref[1] and u[3] > V_ref[2] -> C3 supplies energy
326-
4. u[1] <= V_ref[0] and u[2] <= V_ref[1] and u[3] <= V_ref[2] -> Vs supplies energy
327-
max_index is initialized to -1. List "switch" contains a True if u[k] <= V_ref[k-1] is satisfied.
328-
- Is no True there (i.e. max_index = -1), we are in the first case.
329-
- max_index = k >= 0 means we are in the (k+1)-th case.
330-
So, the actual RHS has key max_index-1 in the dictionary self.switch_f.
331-
In case of using the Switch Estimator, we count the number of switches which illustrates in which case of voltage source we are.
332-
333-
Args:
334-
u (dtype_u): current values
335-
t (float): current time
336-
337-
Returns:
338-
dtype_f: the RHS
339-
"""
340-
341-
f = self.dtype_f(self.init, val=0.0)
342-
f.impl[:] = self.A.dot(u)
343-
344-
if self.t_switch is not None:
345-
f.expl[:] = self.switch_f[self.nswitches]
346-
347-
else:
348-
# proof all switching conditions and find largest index where it drops below V_ref
349-
switch = [True if u[k] <= self.params.V_ref[k - 1] else False for k in range(1, len(u))]
350-
max_index = max([k if switch[k] == True else -1 for k in range(len(switch))])
351-
352-
if max_index == -1:
353-
f.expl[:] = self.switch_f[0]
354-
355-
else:
356-
f.expl[:] = self.switch_f[max_index + 1]
357-
358-
return f
359-
360-
def solve_system(self, rhs, factor, u0, t):
361-
"""
362-
Simple linear solver for (I-factor*A)u = rhs
363-
364-
Args:
365-
rhs (dtype_f): right-hand side for the linear system
366-
factor (float): abbrev. for the local stepsize (or any other factor required)
367-
u0 (dtype_u): initial guess for the iterative solver
368-
t (float): current time (e.g. for time-dependent BCs)
369-
370-
Returns:
371-
dtype_u: solution as mesh
372-
"""
373-
374-
if self.t_switch is not None:
375-
self.A = self.switch_A[self.nswitches]
376-
377-
else:
378-
# proof all switching conditions and find largest index where it drops below V_ref
379-
switch = [True if rhs[k] <= self.params.V_ref[k - 1] else False for k in range(1, len(rhs))]
380-
max_index = max([k if switch[k] == True else -1 for k in range(len(switch))])
381-
if max_index == -1:
382-
self.A = self.switch_A[0]
383-
384-
else:
385-
self.A = self.switch_A[max_index + 1]
386-
387-
me = self.dtype_u(self.init)
388-
me[:] = np.linalg.solve(np.eye(self.params.nvars) - factor * self.A, rhs)
389-
return me
390-
391-
def u_exact(self, t):
392-
"""
393-
Routine to compute the exact solution at time t
394-
395-
Args:
396-
t (float): current time
397-
398-
Returns:
399-
dtype_u: exact solution
400-
"""
401-
assert t == 0, 'ERROR: u_exact only valid for t=0'
402-
403-
me = self.dtype_u(self.init)
404-
405-
me[0] = 0.0 # cL
406-
me[1:] = self.params.alpha * self.params.V_ref # vC's
407-
return me
408-
409-
def get_switching_info(self, u, t):
410-
"""
411-
Provides information about a discrete event for one subinterval.
412-
413-
Args:
414-
u (dtype_u): current values
415-
t (float): current time
416-
417-
Returns:
418-
switch_detected (bool): Indicates if a switch is found or not
419-
m_guess (np.int): Index of collocation node inside one subinterval of where the discrete event was found
420-
vC_switch (list): Contains function values of switching condition (for interpolation)
421-
"""
422-
423-
switch_detected = False
424-
m_guess = -100
425-
break_flag = False
426-
427-
for m in range(len(u)):
428-
for k in range(1, self.params.nvars):
429-
if u[m][k] - self.params.V_ref[k - 1] <= 0:
430-
switch_detected = True
431-
m_guess = m - 1
432-
k_detected = k
433-
break_flag = True
434-
break
435-
436-
if break_flag:
437-
break
438-
439-
vC_switch = (
440-
[u[m][k_detected] - self.params.V_ref[k_detected - 1] for m in range(1, len(u))] if switch_detected else []
441-
)
442-
443-
return switch_detected, m_guess, vC_switch
444-
445-
def count_switches(self):
446-
"""
447-
Counts the number of switches. This function is called when a switch is found inside the range of tolerance
448-
(in switch_estimator.py)
449-
"""
450-
451-
self.nswitches += 1

pySDC/projects/PinTSimE/battery_2capacitors_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def plot_voltages(description, problem, sweeper, recomputed, use_switch_estimato
7171
recomputed (bool): flag if the values after a restart are used or before
7272
use_switch_estimator (bool): flag if the switch estimator wants to be used or not
7373
use_adaptivity (bool): flag if adaptivity wants to be used or not
74-
cwd: current working directory
74+
cwd (str): current working directory
7575
"""
7676

7777
f = open(cwd + 'data/{}_{}_USE{}_USA{}.dat'.format(problem, sweeper, use_switch_estimator, use_adaptivity), 'rb')

0 commit comments

Comments
 (0)