Skip to content

Commit e179d2d

Browse files
committed
Added a battery model with N condensators, removed file Battery_2condensators.py
1 parent a0bedf3 commit e179d2d

File tree

5 files changed

+219
-221
lines changed

5 files changed

+219
-221
lines changed

pySDC/implementations/problem_classes/Battery.py

Lines changed: 186 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class battery(ptype):
1212
Attributes:
1313
A: system matrix, representing the 2 ODEs
1414
t_switch: time point of the switch
15-
SV, SC: states of switching (important for switch estimator)
15+
nswitches: number of switches
1616
"""
1717

1818
def __init__(self, problem_params, dtype_u=mesh, dtype_f=imex_mesh):
@@ -44,8 +44,7 @@ def __init__(self, problem_params, dtype_u=mesh, dtype_f=imex_mesh):
4444

4545
self.A = np.zeros((2, 2))
4646
self.t_switch = None
47-
self.SV = 0
48-
self.SC = 1
47+
self.nswitches = 0
4948

5049
def eval_f(self, u, t):
5150
"""
@@ -145,13 +144,13 @@ def get_switching_info(self, u, t):
145144

146145
return switch_detected, m_guess, vC_switch
147146

148-
def flip_switches(self):
147+
def count_switches(self):
149148
"""
150-
Flips the switches of the circuit to its new state
149+
Counts the number of switches. This function is called when a switch is found inside the range of tolerance
150+
(in switch_estimator.py)
151151
"""
152152

153-
if self.SV == 0 and self.SC == 1:
154-
self.SV, self.SC = 1, 0
153+
self.nswitches += 1
155154

156155

157156
class battery_implicit(battery):
@@ -270,3 +269,183 @@ def solve_system(self, rhs, factor, u0, t):
270269
me[:] = u[:]
271270

272271
return me
272+
273+
274+
class battery_n_condensators(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 = ['ncondensators', '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['ncondensators']
299+
problem_params['nvars'] = n + 1
300+
301+
# invoke super init, passing number of dofs, dtype_u and dtype_f
302+
super(battery_n_condensators, 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

0 commit comments

Comments
 (0)