55from 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
0 commit comments