@@ -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
157156class 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