From 7bf2cd22c50f8ce23b667c111b6bdc8dc9305118 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 15:49:45 -0400 Subject: [PATCH 001/163] test if this is doing what I think its doing --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index e077d7cdb9..113dc52d0f 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -521,7 +521,7 @@ def get_julia_function( var_str = var_str.replace(julia_var, julia_var_short) i_cache += 1 if preallocate is True: - const_and_cache_str += " {} = zeros({}),\n".format( + const_and_cache_str += " {} = dualcache(zeros({})),\n".format( julia_var_short, var_symbol_size ) else: From 53242d0a20fc5132284d87a3037e5c0d9fbd9790 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 15:57:50 -0400 Subject: [PATCH 002/163] remove cs from var_str --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 113dc52d0f..c276d2bf7c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -532,8 +532,8 @@ def get_julia_function( ) # Shorten the name of the constants from id to const_0, const_1, etc. - for long, short in shorter_const_names.items(): - var_str = var_str.replace(long, "cs." + short) + #for long, short in shorter_const_names.items(): + # var_str = var_str.replace(long, "cs." + short) # close the constants and cache string const_and_cache_str += ")\n" From 9254659b24d904600c54be166fe0944761612fcf Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 15:58:54 -0400 Subject: [PATCH 003/163] that was const this is cache --- pybamm/expression_tree/operations/evaluate_julia.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c276d2bf7c..6ae06ea7f2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -532,8 +532,8 @@ def get_julia_function( ) # Shorten the name of the constants from id to const_0, const_1, etc. - #for long, short in shorter_const_names.items(): - # var_str = var_str.replace(long, "cs." + short) + for long, short in shorter_const_names.items(): + var_str = var_str.replace(long, "cs." + short) # close the constants and cache string const_and_cache_str += ")\n" @@ -564,8 +564,8 @@ def get_julia_function( var_str = var_str.replace(result_var, out) # add "cs." to cache names - if preallocate is True: - var_str = var_str.replace("cache", "cs.cache") + #if preallocate is True: + # var_str = var_str.replace("cache", "cs.cache") # line that extracts the input parameters in the right order if input_parameter_order is None: From c1cdc8134a3ca558b53fe74ce39360f37ff0dd6f Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 16:05:56 -0400 Subject: [PATCH 004/163] add get_tmp --- pybamm/expression_tree/operations/evaluate_julia.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 6ae06ea7f2..4b416e2f44 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -409,7 +409,7 @@ def get_julia_function( const_name_short = "const_{}".format(i_const) const_and_cache_str += " {} = {},\n".format(const_name_short, const_value) shorter_const_names[const_name] = const_name_short - + # Pop (get and remove) items from the dictionary of symbols one by one # If they are simple operations (@view, +, -, *, /), replace all future # occurences instead of assigning them. This "inlining" speeds up the computation @@ -448,6 +448,7 @@ def get_julia_function( var_str += "{} = {}\n".format(julia_var, symbol_line) else: symbol_line = symbol_line.replace(" @ ", ", ") + var_str += "{} = get_tmp(cs.{},{}\n".format(julia_var,julia_var,symbol_line) var_str += "mul!({}, {})\n".format(julia_var, symbol_line) # find input parameters elif symbol_line.startswith("inputs"): From 354b09099b714477ac6c02120908c76cb838acd1 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 16:07:49 -0400 Subject: [PATCH 005/163] forgot a parenthesis --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 4b416e2f44..1a5c424ef4 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -448,7 +448,7 @@ def get_julia_function( var_str += "{} = {}\n".format(julia_var, symbol_line) else: symbol_line = symbol_line.replace(" @ ", ", ") - var_str += "{} = get_tmp(cs.{},{}\n".format(julia_var,julia_var,symbol_line) + var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) var_str += "mul!({}, {})\n".format(julia_var, symbol_line) # find input parameters elif symbol_line.startswith("inputs"): From 59c396ce5a512cfe6210d0b54b00523999e82c73 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 16:19:00 -0400 Subject: [PATCH 006/163] slightly hacky fix by splitting symbol_line --- pybamm/expression_tree/operations/evaluate_julia.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 1a5c424ef4..b162d72cb1 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -448,7 +448,8 @@ def get_julia_function( var_str += "{} = {}\n".format(julia_var, symbol_line) else: symbol_line = symbol_line.replace(" @ ", ", ") - var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) + symbol_line_split = symbol_line.split(", ") + var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line_split[1]) var_str += "mul!({}, {})\n".format(julia_var, symbol_line) # find input parameters elif symbol_line.startswith("inputs"): From 5ab80400015793973d2e8b75b6f53bcd80e3ac7d Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 16:38:23 -0400 Subject: [PATCH 007/163] other symbol lines --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index b162d72cb1..62f8aee630 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -421,6 +421,8 @@ def get_julia_function( julia_var = id_to_julia_variable(var_symbol_id, "cache") # Look for lists in the variable symbols. These correpsond to concatenations, so # assign the children to the right parts of the vector + symbol_line_split = symbol_line.split(", ") + var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line_split[1]) if symbol_line[0] == "[" and symbol_line[-1] == "]": # convert to actual list symbol_line = symbol_line[1:-1].split(", ") @@ -448,8 +450,6 @@ def get_julia_function( var_str += "{} = {}\n".format(julia_var, symbol_line) else: symbol_line = symbol_line.replace(" @ ", ", ") - symbol_line_split = symbol_line.split(", ") - var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line_split[1]) var_str += "mul!({}, {})\n".format(julia_var, symbol_line) # find input parameters elif symbol_line.startswith("inputs"): From 0cb2fb4019cb778e508c3a49b364a5d35392ab47 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 16:41:40 -0400 Subject: [PATCH 008/163] other symbol lines --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 62f8aee630..6bdb80972c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -422,7 +422,7 @@ def get_julia_function( # Look for lists in the variable symbols. These correpsond to concatenations, so # assign the children to the right parts of the vector symbol_line_split = symbol_line.split(", ") - var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line_split[1]) + var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) if symbol_line[0] == "[" and symbol_line[-1] == "]": # convert to actual list symbol_line = symbol_line[1:-1].split(", ") From 74ef0eee9312311b1ef4ef72bf236c732eabb45d Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 16:45:26 -0400 Subject: [PATCH 009/163] other symbol lines --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 6bdb80972c..631e39a0d2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -421,8 +421,8 @@ def get_julia_function( julia_var = id_to_julia_variable(var_symbol_id, "cache") # Look for lists in the variable symbols. These correpsond to concatenations, so # assign the children to the right parts of the vector - symbol_line_split = symbol_line.split(", ") - var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) + #symbol_line_split = symbol_line.split(", ") + #var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) if symbol_line[0] == "[" and symbol_line[-1] == "]": # convert to actual list symbol_line = symbol_line[1:-1].split(", ") From e4e28febb59626f5808f383f1b58020e8213a380 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 16:56:13 -0400 Subject: [PATCH 010/163] less hacky way to do it --- pybamm/expression_tree/operations/evaluate_julia.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 631e39a0d2..c8755e0b61 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -511,6 +511,9 @@ def get_julia_function( var_str = " " + var_str var_str = var_str.replace("\n", "\n ") + + cache_initialization_str = "" + # add the cache variables to the cache NamedTuple i_cache = 0 for var_symbol_id, var_symbol_size in var_symbol_sizes.items(): @@ -526,6 +529,7 @@ def get_julia_function( const_and_cache_str += " {} = dualcache(zeros({})),\n".format( julia_var_short, var_symbol_size ) + cache_initialization_str += " {} = cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) else: # Cache variables have not been preallocated var_str = var_str.replace( @@ -593,6 +597,7 @@ def get_julia_function( "begin\n" + const_and_cache_str + function_def + + cache_initialization_str + input_parameter_extraction + var_str ) From 93372171f77f4823b5ee464b8e6e872922354ad5 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 17:00:21 -0400 Subject: [PATCH 011/163] forgot the get_tmp --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c8755e0b61..e2eff09039 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -529,7 +529,7 @@ def get_julia_function( const_and_cache_str += " {} = dualcache(zeros({})),\n".format( julia_var_short, var_symbol_size ) - cache_initialization_str += " {} = cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) else: # Cache variables have not been preallocated var_str = var_str.replace( From b061725fada0b1e058a4d6dcbffa470ae84a3996 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 17:12:48 -0400 Subject: [PATCH 012/163] testing sizes --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index e2eff09039..114fa27bd0 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -526,7 +526,7 @@ def get_julia_function( var_str = var_str.replace(julia_var, julia_var_short) i_cache += 1 if preallocate is True: - const_and_cache_str += " {} = dualcache(zeros({})),\n".format( + const_and_cache_str += " {} = dualcache(zeros({},12)),\n".format( julia_var_short, var_symbol_size ) cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) From 57a73ea7ec4bfce146989c978d4917f685883c03 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Thu, 4 Aug 2022 17:13:52 -0400 Subject: [PATCH 013/163] testing sizes --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 114fa27bd0..5ec0061380 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -526,7 +526,7 @@ def get_julia_function( var_str = var_str.replace(julia_var, julia_var_short) i_cache += 1 if preallocate is True: - const_and_cache_str += " {} = dualcache(zeros({},12)),\n".format( + const_and_cache_str += " {} = dualcache(zeros({}),12),\n".format( julia_var_short, var_symbol_size ) cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) From 4a7fce48928c718b645c8c260a710a75f89f822a Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Fri, 5 Aug 2022 12:45:41 -0400 Subject: [PATCH 014/163] use symcache --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 5ec0061380..395e78c3c2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -526,8 +526,8 @@ def get_julia_function( var_str = var_str.replace(julia_var, julia_var_short) i_cache += 1 if preallocate is True: - const_and_cache_str += " {} = dualcache(zeros({}),12),\n".format( - julia_var_short, var_symbol_size + const_and_cache_str += " {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format( + julia_var_short, var_symbol_size,var_symbol_size ) cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) else: From f56262f1fbdc5a69d1ccfc27416eccec160736b5 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Fri, 5 Aug 2022 14:12:15 -0400 Subject: [PATCH 015/163] introduce cache type flip --- .../operations/evaluate_julia.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 395e78c3c2..b61f1c2617 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -349,6 +349,7 @@ def get_julia_function( len_rhs=None, preallocate=True, round_constants=True, + cache_type="standard" ): """ Converts a pybamm expression tree into pure julia code that will calculate the @@ -372,6 +373,12 @@ def get_julia_function( Whether to write the function in a way that preallocates memory for the output. Default is True, which is faster. Must be False for the function to be modelingtoolkitized. + cache_type : str, optional + The type of cache to use for the function. Must be one of 'standard', 'dual', + or 'symbolic'. If 'standard', the function will be cached in the standard way, + if 'dual', the function will use the dualcache provided by preallocationtools.jl, + and if 'symbolic', the function will use the symcache provided by PyBaMM.jl. Default + is standard, and as of so far, I haven't been able to beat it with performance yet. Returns ------- @@ -526,10 +533,21 @@ def get_julia_function( var_str = var_str.replace(julia_var, julia_var_short) i_cache += 1 if preallocate is True: - const_and_cache_str += " {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format( - julia_var_short, var_symbol_size,var_symbol_size - ) - cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + if cache_type is "symbolic": + const_and_cache_str += " {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format( + julia_var_short, var_symbol_size,var_symbol_size + ) + cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + elif cache_type == "standard": + const_and_cache_str += " {} = zeros({}),\n".format( + julia_var_short, var_symbol_size + ) + elif cache_type == "dual": + const_and_cache_str += " {} = dualcache(zeros({}),12),\n".format( + julia_var_short, var_symbol_size + ) + cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + else: # Cache variables have not been preallocated var_str = var_str.replace( @@ -570,8 +588,9 @@ def get_julia_function( var_str = var_str.replace(result_var, out) # add "cs." to cache names - #if preallocate is True: - # var_str = var_str.replace("cache", "cs.cache") + if preallocate is True: + if cache_type =="standard": + var_str = var_str.replace("cache", "cs.cache") # line that extracts the input parameters in the right order if input_parameter_order is None: From 7dc37b6864fa9b42fc2c969c5a1559fa87850267 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Fri, 5 Aug 2022 14:15:16 -0400 Subject: [PATCH 016/163] typo --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index b61f1c2617..003f2bf54c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -533,7 +533,7 @@ def get_julia_function( var_str = var_str.replace(julia_var, julia_var_short) i_cache += 1 if preallocate is True: - if cache_type is "symbolic": + if cache_type == "symbolic": const_and_cache_str += " {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format( julia_var_short, var_symbol_size,var_symbol_size ) From 7ed3a745b3b7ded51aa1b2cfecb004c41f72036e Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 8 Aug 2022 13:32:44 -0400 Subject: [PATCH 017/163] add gpu support --- pybamm/expression_tree/operations/evaluate_julia.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 003f2bf54c..c05cefaf84 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -374,9 +374,9 @@ def get_julia_function( Default is True, which is faster. Must be False for the function to be modelingtoolkitized. cache_type : str, optional - The type of cache to use for the function. Must be one of 'standard', 'dual', - or 'symbolic'. If 'standard', the function will be cached in the standard way, - if 'dual', the function will use the dualcache provided by preallocationtools.jl, + The type of cache to use for the function. Must be one of 'standard', 'dual', 'symbolic', + or 'gpu'. If 'standard', the function will be cached in the standard way, + If 'dual', the function will use the dualcache provided by preallocationtools.jl, and if 'symbolic', the function will use the symcache provided by PyBaMM.jl. Default is standard, and as of so far, I haven't been able to beat it with performance yet. @@ -414,7 +414,10 @@ def get_julia_function( for i_const, (symbol_id, const_value) in enumerate(constants.items()): const_name = id_to_julia_variable(symbol_id, "const") const_name_short = "const_{}".format(i_const) - const_and_cache_str += " {} = {},\n".format(const_name_short, const_value) + if cache_type=="gpu": + const_and_cache_str += " {} = cu({}),\n".format(const_name_short, const_value) + else: + const_and_cache_str += " {} = {},\n".format(const_name_short, const_value) shorter_const_names[const_name] = const_name_short # Pop (get and remove) items from the dictionary of symbols one by one @@ -547,6 +550,8 @@ def get_julia_function( julia_var_short, var_symbol_size ) cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + elif cache_type == "gpu": + const_and_cache_str+=" {} = CUDA.zeros({}),\n".format(julia_var_short,julia_symbol_size) else: # Cache variables have not been preallocated From 30c1217fc4285421de03728f067747e4bab042cb Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 8 Aug 2022 13:38:06 -0400 Subject: [PATCH 018/163] fix typo for gpu --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c05cefaf84..f3a444e25b 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -551,7 +551,7 @@ def get_julia_function( ) cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) elif cache_type == "gpu": - const_and_cache_str+=" {} = CUDA.zeros({}),\n".format(julia_var_short,julia_symbol_size) + const_and_cache_str+=" {} = CUDA.zeros({}),\n".format(julia_var_short,var_symbol_size) else: # Cache variables have not been preallocated From 6a330a719dfe355d090dbff8533e8cf26ea26e12 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 8 Aug 2022 13:45:00 -0400 Subject: [PATCH 019/163] add cs. for gpu --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index f3a444e25b..dc5513da2c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -594,7 +594,7 @@ def get_julia_function( # add "cs." to cache names if preallocate is True: - if cache_type =="standard": + if cache_type in ["standard","gpu"]: var_str = var_str.replace("cache", "cs.cache") # line that extracts the input parameters in the right order From 0ed228d871a7af64e55a55c87a7ba19bbd5b1490 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 9 Aug 2022 17:21:40 -0400 Subject: [PATCH 020/163] specify preallocationtools --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index dc5513da2c..143641bd3d 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -549,7 +549,7 @@ def get_julia_function( const_and_cache_str += " {} = dualcache(zeros({}),12),\n".format( julia_var_short, var_symbol_size ) - cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + cache_initialization_str += " {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) elif cache_type == "gpu": const_and_cache_str+=" {} = CUDA.zeros({}),\n".format(julia_var_short,var_symbol_size) From 65144f7ce5350dd96e57d897d4a7af5060cb948b Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 14 Aug 2022 14:36:45 -0400 Subject: [PATCH 021/163] try pybamm jacobian --- pybamm/models/base_model.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index d9355b524c..36ca45cb86 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1104,6 +1104,7 @@ def generate_julia_diffeq( input_parameter_order=None, get_consistent_ics_solver=None, dae_type="semi-explicit", + generate_jacobian=False, **kwargs, ): """ @@ -1174,6 +1175,17 @@ def generate_julia_diffeq( ics_str = ics_str.replace("(dy, y, p, t)", "(u0, p)") ics_str = ics_str.replace("dy", "u0") + if generate_jacobian: + expr = pybamm.jac(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic)) + jac_str = pybamm.get_julia_function( + expr, + funcname="jac_"+name, + input_parameter_order=input_parameter_order, + **kwargs, + ) + jac_str.replace("dy","J") + return eqn_str,ics_str,jac_str + return eqn_str, ics_str def latexify(self, filename=None, newline=True): From b2a0c918dfb9865e738f06ad3675efcc569c2e18 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 14 Aug 2022 14:40:08 -0400 Subject: [PATCH 022/163] try pybamm jacobian --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 36ca45cb86..6a38c96612 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1176,7 +1176,7 @@ def generate_julia_diffeq( ics_str = ics_str.replace("dy", "u0") if generate_jacobian: - expr = pybamm.jac(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic)) + expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(self.y0.full) jac_str = pybamm.get_julia_function( expr, funcname="jac_"+name, From 5a8f4176ff8dc1608be787fe996e2e807c16f308 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 14 Aug 2022 14:42:04 -0400 Subject: [PATCH 023/163] try pybamm jacobian --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 6a38c96612..ec7731e6ec 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1176,7 +1176,7 @@ def generate_julia_diffeq( ics_str = ics_str.replace("dy", "u0") if generate_jacobian: - expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(self.y0.full) + expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(ics) jac_str = pybamm.get_julia_function( expr, funcname="jac_"+name, From 6c167e2cc456fed6ff22655949e62728ddf96a7c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 14 Aug 2022 15:02:50 -0400 Subject: [PATCH 024/163] try pybamm jacobian --- pybamm/models/base_model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index ec7731e6ec..fa030de715 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1176,7 +1176,9 @@ def generate_julia_diffeq( ics_str = ics_str.replace("dy", "u0") if generate_jacobian: - expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(ics) + size_state = self.concatenated_intial_conditions.size + state_vector = pybamm.StateVector(slice(0,(size_state-1))) + expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) jac_str = pybamm.get_julia_function( expr, funcname="jac_"+name, From f29eec33b3264d57b582dc2622ba85b2ed3acffe Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 14 Aug 2022 15:04:07 -0400 Subject: [PATCH 025/163] try pybamm jacobian --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index fa030de715..f8196d68c1 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1176,7 +1176,7 @@ def generate_julia_diffeq( ics_str = ics_str.replace("dy", "u0") if generate_jacobian: - size_state = self.concatenated_intial_conditions.size + size_state = self.concatenated_initial_conditions.size state_vector = pybamm.StateVector(slice(0,(size_state-1))) expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) jac_str = pybamm.get_julia_function( From 8ccb9b496cff66378848b267aae3f2501fed27f5 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 14 Aug 2022 16:25:52 -0400 Subject: [PATCH 026/163] try pybamm jacobian --- pybamm/expression_tree/operations/evaluate_julia.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 143641bd3d..61f61530a8 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -103,6 +103,7 @@ def find_symbols( m, n, ) + variable_symbol_sizes[symbol.id] = symbol.size elif value.shape == (1, 1): # Extract value if array has only one entry constant_symbols[symbol.id] = value[0, 0] From d19d3351c7104dafeee8af7ab204edbe644680ec Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 15 Aug 2022 10:26:44 -0400 Subject: [PATCH 027/163] change sizing --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 61f61530a8..292d2b6054 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -103,7 +103,7 @@ def find_symbols( m, n, ) - variable_symbol_sizes[symbol.id] = symbol.size + variable_symbol_sizes[symbol.id] = symbol.shape elif value.shape == (1, 1): # Extract value if array has only one entry constant_symbols[symbol.id] = value[0, 0] From f017370ceab420279dc32f29b553f1597c633450 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 15 Aug 2022 11:21:09 -0400 Subject: [PATCH 028/163] change something --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 292d2b6054..90fa3c592d 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -103,7 +103,7 @@ def find_symbols( m, n, ) - variable_symbol_sizes[symbol.id] = symbol.shape + variable_symbol_sizes[symbol.id] = -1 elif value.shape == (1, 1): # Extract value if array has only one entry constant_symbols[symbol.id] = value[0, 0] From d08422b4ba85a54b3230940967817fda9df3a98d Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 11 Sep 2022 17:40:02 -0400 Subject: [PATCH 029/163] updating julia builder --- .../operations/evaluate_julia_new.py | 713 ++++++++++++++++++ 1 file changed, 713 insertions(+) create mode 100644 pybamm/expression_tree/operations/evaluate_julia_new.py diff --git a/pybamm/expression_tree/operations/evaluate_julia_new.py b/pybamm/expression_tree/operations/evaluate_julia_new.py new file mode 100644 index 0000000000..0f54d87a07 --- /dev/null +++ b/pybamm/expression_tree/operations/evaluate_julia_new.py @@ -0,0 +1,713 @@ +# +# Compile a PyBaMM expression tree to Julia Code +# +import pybamm +import numpy as np +import numpy +from scipy import special +import scipy +from collections import OrderedDict +from multimethod import multimethod +from math import floor + +def is_constant_and_can_evaluate(symbol): + """ + Returns True if symbol is constant and evaluation does not raise any errors. + Returns False otherwise. + An example of a constant symbol that cannot be "evaluated" is PrimaryBroadcast(0). + """ + if symbol.is_constant(): + try: + symbol.evaluate() + return True + except NotImplementedError: + return False + else: + return False + +#BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH +class JuliaBinaryOperation(object): + def __init__(self,left_input,right_input,output,shape): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + +#MatMul and Inner Product are not really the same as the bitwisebinary operations. +class JuliaMatrixMultiplication(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + +class JuliaBitwiseBinaryOperation(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape,operator): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + self.operator = operator + +class JuliaAddition(JuliaBinaryOperation): + pass + +class JuliaSubtraction(JuliaBinaryOperation): + pass + +class JuliaMultiplication(JuliaBinaryOperation): + pass + +class JuliaDivision(JuliaBinaryOperation): + pass + +class JuliaPower(JuliaBinaryOperation): + pass + +#MinMax is special because it does both min and max. Could be folded into JuliaBitwiseBinaryOperation once I do that +class JuliaMinMax(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape,name): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + self.name = name + +#FUNCTIONS +##All Functions Return the same number of arguments they take in, except for minimum and maximum. +class JuliaFunction(object): + pass + +class JuliaBroadcastableFunction(JuliaFunction): + def __init__(self,name,input,output,shape): + self.name = name + self.input = input + self.output = output + self.shape = shape + + +#Index is a little weird, so it just sits on its own. +class JuliaIndex(object): + def __init__(self,input,output,index): + self.input = input + self.output = output + self.index = index + if type(index) is slice: + if type(index.step) is None: + self.shape = (slice.start-slice.stop,1) + elif type(index.step) is int: + self.shape = (floor((slice.stop-slice.start)/slice.step),1) + elif type(index) is int: + self.shape = (1,1) + else: + raise NotImplementedError("index must be slice or int") + + + +#Values and Constants -- I will need to change this to inputs, due to t, y, and p. +class JuliaValue(object): + pass + +class JuliaConstant(JuliaValue): + def __init__(self,id,value): + self.id = id + self.value = value + self.shape = value.shape + +class JuliaStateVector(JuliaValue): + def __init__(self,id,loc,shape): + self.id = id + self.loc = loc + self.shape = shape + +class JuliaScalar(JuliaConstant): + def __init__(self,id,value): + self.id = id + self.value = float(value) + self.shape = (1,1) + +class JuliaTime(JuliaScalar): + def __init__(self,id): + self.id = id + self.shape = (1,1) + self.value = None + + +#CONCATENATIONS +class JuliaConcatenation(object): + def __init__(self,output,shape,children): + self.output = output + self.shape = shape + self.children = children + +class JuliaNumpyConcatenation(JuliaConcatenation): + pass + +#NOTE: CURRENTLY THIS BEHAVES EXACTLY LIKE NUMPYCONCATENATION +class JuliaSparseStack(JuliaConcatenation): + pass + + + + +class JuliaConverter(object): + def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit"): + assert not ismtk + + #Characteristics + self._cache_type = cache_type + self._ismtk=ismtk + self._jacobian_type=jacobian_type + self._preallocate=preallocate + self._dae_type = dae_type + + #"Caches" + #Stores Constants to be Declared in the initial cache + #insight: everything is just a line of code + + #INTERMEDIATE: A List of Stuff to do. Keys are ID's and lists are variable names. + self._intermediate = OrderedDict() + + #Cache Dict and Const Dict Host Julia Variable Names to be used to generate the code. + self._cache_dict = OrderedDict() + self._const_dict = OrderedDict() + + self._cache_id = 0 + self._const_id = 0 + + self._cache_and_const_string = "" + self.function_definition = "" + self._function_string = "" + self._return_string = "" + + #know where to go to find a variable. this could be smoother, there will need to be a ton of boilerplate here. + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaConcatenation): + return self._cache_dict[julia_symbol.output] + + @multimethod + def get_result_variable_name(self, julia_symbol:JuliaBinaryOperation): + return self._cache_dict[julia_symbol.output] + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaConstant): + return self._const_dict[julia_symbol.id] + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaScalar): + return julia_symbol.value + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaTime): + return "t" + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaStateVector): + start = julia_symbol.loc[0]+1 + end = julia_symbol.loc[1] + return "(@view y[{}:{}])".format(start,end) + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaBroadcastableFunction): + return self._cache_dict[julia_symbol.output] + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaIndex): + lower_var = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + index = julia_symbol.index + if type(index) is int: + return "{}[{}]".format(lower_var,index+1) + elif type(index) is slice: + if index.step is None: + return "(@view {}[{}:{}])".format(lower_var,index.start+1,index.stop) + elif type(index.step) is int: + return "(@view {}[{}:{}:{}])".format(lower_var,index.start+1,index.step,index.stop) + else: + raise NotImplementedError("Step has to be an integer.") + else: + raise NotImplementedError("Step must be a slice or an int") + + #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. + def break_down_binary(self,symbol): + #Check for constant + assert not is_constant_and_can_evaluate(symbol) + + #We know that this should only have 2 children + assert len(symbol.children)==2 + + #take care of the kids first (this is recursive but multiple-dispatch recursive which is cool) + id_left = self.convert_tree_to_intermediate(symbol.children[0]) + id_right = self.convert_tree_to_intermediate(symbol.children[1]) + my_id = symbol.id + return id_left,id_right,my_id + + def break_down_concatenation(self,symbol): + child_ids = [] + for child in symbol.children: + child_id = self.convert_tree_to_intermediate(child) + child_ids.append(child_id) + first_id = child_ids[0] + num_cols = self._intermediate[first_id].shape[1] + num_rows = 0 + for child_id in child_ids: + child_shape = self._intermediate[child_id].shape + assert num_cols == child_shape[1] + num_rows+=child_shape[0] + shape = (num_rows,num_cols) + return child_ids,shape + + + #Convert-Trees go here + + #Binary trees constructors. All follow the pattern of mat-mul. They need to find their shapes, assuming that the shapes of the nodes one level below them in the expression tree have already been computed. + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.NumpyConcatenation): + my_id = symbol.id + children_julia,shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaNumpyConcatenation(my_id,shape,children_julia) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.SparseStack): + my_id = symbol.id + children_julia,shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaSparseStack(my_id,shape,children_julia) + return my_id + + + @multimethod + def convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): + #Break down the binary tree + id_left,id_right,my_id = self.break_down_binary(symbol) + left_shape = self._intermediate[id_left].shape + right_shape = self._intermediate[id_right].shape + my_shape = (left_shape[0],right_shape[1]) + #Cache the result. + self._intermediate[my_id] = JuliaMatrixMultiplication(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) + return my_id + + #Apparently an inner product is a hadamard product in pybamm + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Inner): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Division): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol: pybamm.Addition): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Minimum): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min") + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Maximum): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max") + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Power): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.EqualHeaviside): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,">=") + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.NotEqualHeaviside): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,">") + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Index): + assert len(symbol.children)==1 + id_lower = self.convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + index = symbol.index + self._intermediate[my_id] = JuliaIndex(id_lower,my_id,index) + return my_id + + + + #Convenience function for operations which can have + def find_the_nonscalar(self,id_left,id_right): + left_type = type(self._intermediate[id_left]) + right_type = type(self._intermediate[id_right]) + if issubclass(left_type,JuliaScalar): + return self._intermediate[id_right].shape + elif issubclass(right_type,JuliaScalar): + return self._intermediate[id_left].shape + else: + return self.same_shape(id_left,id_right) + + #to find the shape, there are a number of elements that should just have the shame shape as their children. This function removes boilerplate by implementing those cases + def same_shape(self,id_left,id_right): + left_shape = self._intermediate[id_left].shape + right_shape = self._intermediate[id_right].shape + assert left_shape==right_shape + return left_shape + + + #Functions + #Broadcastable functions have 1 input and 1 output, and the input and output have the same shape. The hard part is that we have to know which is which and pybamm doesn't differentiate between the two. So, we have to do that with an if statement. + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Function): + my_jl_name = symbol.julia_name + assert len(symbol.children)==1 + my_shape = symbol.children[0].shape + input = self.convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaBroadcastableFunction(my_jl_name,input,my_id,my_shape) + return my_id + + + + #Constants and Values. There are only 2 of these. They must know their own shapes. + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Matrix): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + if value.shape==(1,1): + self._intermediate[my_id] = JuliaScalar(my_id,value) + else: + self._intermediate[my_id] = JuliaConstant(my_id,value) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Vector): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + if value.shape==(1,1): + self._intermediate[my_id] = JuliaScalar(my_id,value) + else: + self._intermediate[my_id] = JuliaConstant(my_id,value) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Scalar): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + self._intermediate[my_id] = JuliaScalar(my_id,value) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Time): + my_id = symbol.id + self._intermediate[my_id] = JuliaTime(my_id) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.StateVector): + my_id = symbol.id + first_point = symbol.first_point + last_point = symbol.last_point + points = (first_point,last_point) + shape = symbol.shape + self._intermediate[my_id] = JuliaStateVector(id,points,shape) + return my_id + + #utilities for code conversion + def get_variables_for_binary_tree(self,julia_symbol): + left_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.left_input]) + right_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.right_input]) + result_var_name = self.get_result_variable_name(julia_symbol) + return left_input_var_name,right_input_var_name,result_var_name + + #convert intermediates to code. Again, all binary trees follow the same pattern so we just define a function to break them down, and then use the MD to find out what code to generate. + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaConcatenation): + input_var_names = [] + num_cols = julia_symbol.shape[1] + my_name = self.get_result_variable_name(julia_symbol) + + #assume we don't have tensors. Already asserted that concatenations have to have the same width. + if num_cols==1: + right_parenthesis = "]" + vec=True + else: + right_parenthesis = ",:]" + vec=False + #do the 0th one outside of the loop to initialize + child = julia_symbol.children[0] + child_var = self._intermediate[child] + child_var_name = self.get_result_variable_name(self._intermediate[child]) + start_row = 1 + if child_var.shape[0] == 0: + end_row = 1 + code = "" + elif child_var.shape[0] == 1: + end_row = 1 + if vec: + code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + code = "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + start_row = 1 + end_row = child_var.shape[0] + code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + + for child in julia_symbol.children[1:]: + child_var = self._intermediate[child] + child_var_name = self.get_result_variable_name(self._intermediate[child]) + if child_var.shape[0] == 0: + continue + elif child_var.shape[0] == 1: + start_row = end_row+1 + end_row = start_row+1 + if vec: + code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + code += "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + start_row = end_row+1 + end_row = start_row+child_var.shape[0]-1 + code += "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + + self._function_string+=code + return 0 + + + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): + result_var_name = self.get_result_variable_name(julia_symbol) + input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaMinMax): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) + else: + code = "{} = {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaPower): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {}.^{}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {}.^{})\n".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaBitwiseBinaryOperation): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {}.{}{}\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) + else: + code = "{} = {}.{}{})\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) + self._function_string+=code + return 0 + + #Cache and Const Creation + @multimethod + def create_cache(self,symbol): + my_id = symbol.output + + cache_shape = self._intermediate[my_id].shape + + cache_id = self._cache_id+1 + self._cache_id = cache_id + + cache_name = "cache_{}".format(cache_id) + self._cache_dict[symbol.output] = "cs."+cache_name + + if self._cache_type=="standard": + if cache_shape[1] == 1: + cache_shape = "({})".format(cache_shape[0]) + self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) + return 0 + else: + raise NotImplementedError("uh oh") + + + + def create_const(self,symbol): + my_id = symbol.id + const_id = self._const_id+1 + self._const_id = const_id + const_name = "const_{}".format(const_id) + self._const_dict[my_id] = "cs."+const_name + mat_value = symbol.value + print(type(mat_value)) + val_line = self.write_const(mat_value) + const_line = const_name+" = {},\n".format(val_line) + self._cache_and_const_string+=const_line + return 0 + + @multimethod + def write_const(self,mat_value:numpy.ndarray): + return mat_value + + @multimethod + def write_const(self,value:scipy.sparse._csr.csr_matrix): + row, col, data = scipy.sparse.find(value) + m, n = value.shape + np.set_printoptions( + threshold=max(np.get_printoptions()["threshold"], len(row) + 10) + ) + + val_string = "sparse({}, {}, {}, {}, {})".format( + np.array2string(row + 1, separator=","), + np.array2string(col + 1, separator=","), + np.array2string(data, separator=","), + m, + n, + ) + return val_string + + #Just get something working here, so can start actual testing + def write_function_easy(self): + #start with the closure + self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string + self._cache_and_const_string += ")\n" + + + top = self._intermediate[next(reversed(self._intermediate))] + top_var_name = self.get_result_variable_name(top) + my_shape = top.shape + if my_shape[1] != 1: + self._function_string += "J[:,:] .= {}\nend\nend".format(top_var_name) + self._function_string = "function model(J,y,p,t)\n" + self._function_string + else: + self._function_string+= "dy[:] .= {}\nend\nend".format(top_var_name) + self._function_string = "function model(dy,y,p,t)\n" + self._function_string + + + + return 0 + + + + + + #rework this at some point + def build_julia_code(self): + for entry in self._intermediate.values(): + print(entry) + if issubclass(type(entry),JuliaBinaryOperation): + self.create_cache(entry) + self.convert_intermediate_to_code(entry) + elif type(entry) is JuliaConstant: + self.create_const(entry) + elif type(entry) is JuliaIndex: + continue + elif type(entry) is JuliaStateVector: + continue + elif type(entry) is JuliaScalar: + continue + elif type(entry) is JuliaBroadcastableFunction: + self.create_cache(entry) + self.convert_intermediate_to_code(entry) + elif type(entry) is JuliaTime: + continue + elif type(entry) in [JuliaNumpyConcatenation,JuliaSparseStack]: + self.create_cache(entry) + self.convert_intermediate_to_code(entry) + else: + raise NotImplementedError("uh oh") + self.write_function_easy() + string = self._cache_and_const_string+self._function_string + return string + +def fuck_me(): + print("fuck") + \ No newline at end of file From cce4de44efcdf44620b0cd8aa6d405fa82cdf997 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 12 Sep 2022 16:58:45 -0400 Subject: [PATCH 030/163] new code passing tests --- pybamm/__init__.py | 3 +- .../operations/evaluate_julia.py | 1844 +++++++---------- .../operations/evaluate_julia_new.py | 713 ------- .../operations/evaluate_julia_old.py | 1142 ++++++++++ .../test_operations/test_evaluate_julia.py | 30 +- 5 files changed, 1928 insertions(+), 1804 deletions(-) delete mode 100644 pybamm/expression_tree/operations/evaluate_julia_new.py create mode 100644 pybamm/expression_tree/operations/evaluate_julia_old.py diff --git a/pybamm/__init__.py b/pybamm/__init__.py index c9d05cc53f..dff7c69ac9 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -91,8 +91,7 @@ from .expression_tree.operations.unpack_symbols import SymbolUnpacker from .expression_tree.operations.replace_symbols import SymbolReplacer from .expression_tree.operations.evaluate_julia import ( - get_julia_function, - get_julia_mtk_model, + JuliaConverter, ) # diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 90fa3c592d..3dbced2d5e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -1,24 +1,14 @@ # -# Write a symbol to Julia +# Compile a PyBaMM expression tree to Julia Code # import pybamm - import numpy as np -import scipy.sparse +import numpy +from scipy import special +import scipy from collections import OrderedDict - -import numbers - - -def id_to_julia_variable(symbol_id, prefix): - """ - This function defines the format for the julia variable names used in find_symbols - and to_julia. Variable names are based on a nodes' id to make them unique - """ - var_format = prefix + "_{:05d}" - # Need to replace "-" character to make them valid julia variable names - return var_format.format(symbol_id).replace("-", "m") - +from multimethod import multimethod +from math import floor def is_constant_and_can_evaluate(symbol): """ @@ -35,1097 +25,803 @@ def is_constant_and_can_evaluate(symbol): else: return False - -def find_symbols( - symbol, - constant_symbols, - variable_symbols, - variable_symbol_sizes, - round_constants=True, -): - """ - This function converts an expression tree to a dictionary of node id's and strings - specifying valid julia code to calculate that nodes value, given y and t. - - The function distinguishes between nodes that represent constant nodes in the tree - (e.g. a pybamm.Matrix), and those that are variable (e.g. subtrees that contain - pybamm.StateVector). The former are put in `constant_symbols`, the latter in - `variable_symbols` - - Note that it is important that the arguments `constant_symbols` and - `variable_symbols` be and *ordered* dict, since the final ordering of the code lines - are important for the calculations. A dict is specified rather than a list so that - identical subtrees (which give identical id's) are not recalculated in the code - - Parameters - ---------- - symbol : :class:`pybamm.Symbol` - The symbol or expression tree to convert - - constant_symbol : collections.OrderedDict - The output dictionary of constant symbol ids to lines of code - - variable_symbol : collections.OrderedDict - The output dictionary of variable (with y or t) symbol ids to lines of code - - variable_symbol_sizes : collections.OrderedDict - The output dictionary of variable (with y or t) symbol ids to size of that - variable, for caching - - """ - # ignore broadcasts for now - if isinstance(symbol, pybamm.Broadcast): - symbol = symbol.child - if is_constant_and_can_evaluate(symbol): - value = symbol.evaluate() - if round_constants: - value = np.round(value, decimals=11) - if not isinstance(value, numbers.Number): - if scipy.sparse.issparse(value): - # Create Julia SparseArray - row, col, data = scipy.sparse.find(value) - if round_constants: - data = np.round(data, decimals=11) - m, n = value.shape - # Set print options large enough to avoid ellipsis - # at least as big is len(row) = len(col) = len(data) - np.set_printoptions( - threshold=max(np.get_printoptions()["threshold"], len(row) + 10) - ) - # increase precision for printing - np.set_printoptions(precision=20) - # add 1 to correct for 1-indexing in Julia - # use array2string so that commas are included - constant_symbols[symbol.id] = "sparse({}, {}, {}, {}, {})".format( - np.array2string(row + 1, separator=","), - np.array2string(col + 1, separator=","), - np.array2string(data, separator=","), - m, - n, - ) - variable_symbol_sizes[symbol.id] = -1 - elif value.shape == (1, 1): - # Extract value if array has only one entry - constant_symbols[symbol.id] = value[0, 0] - variable_symbol_sizes[symbol.id] = 1 - elif value.shape[1] == 1: - # Set print options large enough to avoid ellipsis - # at least as big as len(row) = len(col) = len(data) - np.set_printoptions( - threshold=max( - np.get_printoptions()["threshold"], value.shape[0] + 10 - ) - ) - # Flatten a 1D array - constant_symbols[symbol.id] = np.array2string( - value.flatten(), separator="," - ) - variable_symbol_sizes[symbol.id] = symbol.shape[0] - else: - constant_symbols[symbol.id] = value - # No need to save the size as it will not need to be used - return - - # process children recursively - for child in symbol.children: - find_symbols( - child, - constant_symbols, - variable_symbols, - variable_symbol_sizes, - round_constants=round_constants, - ) - - # calculate the variable names that will hold the result of calculating the - # children variables - children_vars = [] - for child in symbol.children: - if isinstance(child, pybamm.Broadcast): - child = child.child - if is_constant_and_can_evaluate(child): - child_eval = child.evaluate() - if isinstance(child_eval, numbers.Number): - children_vars.append(str(child_eval)) - else: - children_vars.append(id_to_julia_variable(child.id, "const")) +#BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH +class JuliaBinaryOperation(object): + def __init__(self,left_input,right_input,output,shape): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + +#MatMul and Inner Product are not really the same as the bitwisebinary operations. +class JuliaMatrixMultiplication(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + +class JuliaBitwiseBinaryOperation(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape,operator): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + self.operator = operator + +class JuliaAddition(JuliaBinaryOperation): + pass + +class JuliaSubtraction(JuliaBinaryOperation): + pass + +class JuliaMultiplication(JuliaBinaryOperation): + pass + +class JuliaDivision(JuliaBinaryOperation): + pass + +class JuliaPower(JuliaBinaryOperation): + pass + +#MinMax is special because it does both min and max. Could be folded into JuliaBitwiseBinaryOperation once I do that +class JuliaMinMax(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape,name): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + self.name = name + +#FUNCTIONS +##All Functions Return the same number of arguments they take in, except for minimum and maximum. +class JuliaFunction(object): + pass + +class JuliaBroadcastableFunction(JuliaFunction): + def __init__(self,name,input,output,shape): + self.name = name + self.input = input + self.output = output + self.shape = shape + +class JuliaMinimumMaximum(JuliaBroadcastableFunction): + pass + + +#Index is a little weird, so it just sits on its own. +class JuliaIndex(object): + def __init__(self,input,output,index): + self.input = input + self.output = output + self.index = index + if type(index) is slice: + if type(index.step) is None: + self.shape = (slice.start-slice.stop,1) + elif type(index.step) is int: + self.shape = (floor((slice.stop-slice.start)/slice.step),1) + elif type(index) is int: + self.shape = (1,1) else: - children_vars.append(id_to_julia_variable(child.id, "cache")) - - if isinstance(symbol, pybamm.BinaryOperator): - # TODO: we can pass through a dummy y and t to get the type and then hardcode - # the right line, avoiding these checks - if isinstance(symbol, pybamm.MatrixMultiplication): - symbol_str = "{0} @ {1}".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Inner): - symbol_str = "{0} * {1}".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Minimum): - symbol_str = "min({},{})".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Maximum): - symbol_str = "max({},{})".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Power): - # julia uses ^ instead of ** for power - # include dot for elementwise operations - symbol_str = children_vars[0] + " .^ " + children_vars[1] - else: - # all other operations use the same symbol - symbol_str = children_vars[0] + " " + symbol.name + " " + children_vars[1] - - elif isinstance(symbol, pybamm.UnaryOperator): - # Index has a different syntax than other univariate operations - if isinstance(symbol, pybamm.Index): - # Because of how julia indexing works, add 1 to the start, but not to the - # stop - symbol_str = "{}[{}:{}]".format( - children_vars[0], symbol.slice.start + 1, symbol.slice.stop - ) - elif isinstance(symbol, pybamm.Gradient): - symbol_str = "grad_{}({})".format(tuple(symbol.domain), children_vars[0]) - elif isinstance(symbol, pybamm.Divergence): - symbol_str = "div_{}({})".format(tuple(symbol.domain), children_vars[0]) - elif isinstance(symbol, pybamm.Broadcast): - # ignore broadcasts for now - symbol_str = children_vars[0] - elif isinstance(symbol, pybamm.BoundaryValue): - symbol_str = "boundary_value_{}({})".format(symbol.side, children_vars[0]) - else: - symbol_str = symbol.name + children_vars[0] - - elif isinstance(symbol, pybamm.Function): - # write functions directly - symbol_str = "{}({})".format(symbol.julia_name, ", ".join(children_vars)) - - elif isinstance(symbol, (pybamm.Variable, pybamm.ConcatenationVariable)): - # No need to do anything if a Variable is found - return - - elif isinstance(symbol, pybamm.Concatenation): - if isinstance(symbol, (pybamm.NumpyConcatenation, pybamm.SparseStack)): - # return a list of the children variables, which will be converted to a - # line by line assignment - # return this as a string so that other functionality still works - # also save sizes - symbol_str = "[" - for child in children_vars: - child_id = child[6:].replace("m", "-") - size = variable_symbol_sizes[int(child_id)] - symbol_str += "{}::{}, ".format(size, child) - symbol_str = symbol_str[:-2] + "]" - - # DomainConcatenation specifies a particular ordering for the concatenation, - # which we must follow - elif isinstance(symbol, pybamm.DomainConcatenation): - if symbol.secondary_dimensions_npts == 1: - all_child_vectors = children_vars - all_child_sizes = [ - variable_symbol_sizes[int(child[6:].replace("m", "-"))] - for child in children_vars - ] + raise NotImplementedError("index must be slice or int") + + + +#Values and Constants -- I will need to change this to inputs, due to t, y, and p. +class JuliaValue(object): + pass + +class JuliaConstant(JuliaValue): + def __init__(self,id,value): + self.id = id + self.value = value + self.shape = value.shape + +class JuliaStateVector(JuliaValue): + def __init__(self,id,loc,shape): + self.id = id + self.loc = loc + self.shape = shape + +class JuliaScalar(JuliaConstant): + def __init__(self,id,value): + self.id = id + self.value = float(value) + self.shape = (1,1) + +class JuliaTime(JuliaScalar): + def __init__(self,id): + self.id = id + self.shape = (1,1) + +class JuliaInput(JuliaScalar): + def __init__(self,id,name): + self.id = id + self.shape = (1,1) + self.name = name + + + +#CONCATENATIONS +class JuliaConcatenation(object): + def __init__(self,output,shape,children): + self.output = output + self.shape = shape + self.children = children + +class JuliaNumpyConcatenation(JuliaConcatenation): + pass + +#NOTE: CURRENTLY THIS BEHAVES EXACTLY LIKE NUMPYCONCATENATION +class JuliaSparseStack(JuliaConcatenation): + pass + + +class JuliaDomainConcatenation(JuliaConcatenation): + def __init__(self,output,shape,children,secondary_dimension_npts,children_slices): + self.output = output + self.shape = shape + self.children = children + self.secondary_dimension_npts = secondary_dimension_npts + self.children_slices = children_slices + + + + +class JuliaConverter(object): + def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit"): + assert not ismtk + + #Characteristics + self._cache_type = cache_type + self._ismtk=ismtk + self._jacobian_type=jacobian_type + self._preallocate=preallocate + self._dae_type = dae_type + + #"Caches" + #Stores Constants to be Declared in the initial cache + #insight: everything is just a line of code + + #INTERMEDIATE: A List of Stuff to do. Keys are ID's and lists are variable names. + self._intermediate = OrderedDict() + + #Cache Dict and Const Dict Host Julia Variable Names to be used to generate the code. + self._cache_dict = OrderedDict() + self._const_dict = OrderedDict() + + self._parameter_dict = OrderedDict() + + self._cache_id = 0 + self._const_id = 0 + + self._cache_and_const_string = "" + self.function_definition = "" + self._function_string = "" + self._return_string = "" + + #know where to go to find a variable. this could be smoother, there will need to be a ton of boilerplate here. + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaConcatenation): + return self._cache_dict[julia_symbol.output] + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaMinimumMaximum): + return self._cache_dict[julia_symbol.output] + + @multimethod + def get_result_variable_name(self, julia_symbol:JuliaBinaryOperation): + return self._cache_dict[julia_symbol.output] + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaConstant): + return self._const_dict[julia_symbol.id] + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaScalar): + return julia_symbol.value + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaTime): + return "t" + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaInput): + return julia_symbol.name + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaStateVector): + start = julia_symbol.loc[0]+1 + end = julia_symbol.loc[1] + return "(@view y[{}:{}])".format(start,end) + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaBroadcastableFunction): + return self._cache_dict[julia_symbol.output] + + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaIndex): + lower_var = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + index = julia_symbol.index + if type(index) is int: + return "{}[{}]".format(lower_var,index+1) + elif type(index) is slice: + if index.step is None: + return "(@view {}[{}:{}])".format(lower_var,index.start+1,index.stop) + elif type(index.step) is int: + return "(@view {}[{}:{}:{}])".format(lower_var,index.start+1,index.step,index.stop) else: - slice_starts = [] - all_child_vectors = [] - all_child_sizes = [] - for i in range(symbol.secondary_dimensions_npts): - child_vectors = [] - child_sizes = [] - for child_var, slices in zip( - children_vars, symbol._children_slices - ): - for child_dom, child_slice in slices.items(): - slice_starts.append(symbol._slices[child_dom][i].start) - # add 1 to slice start to account for julia indexing - child_vectors.append( - "@view {}[{}:{}]".format( - child_var, - child_slice[i].start + 1, - child_slice[i].stop, - ) - ) - child_sizes.append( - child_slice[i].stop - child_slice[i].start - ) - all_child_vectors.extend( - [v for _, v in sorted(zip(slice_starts, child_vectors))] - ) - all_child_sizes.extend( - [v for _, v in sorted(zip(slice_starts, child_sizes))] - ) - # return a list of the children variables, which will be converted to a - # line by line assignment - # return this as a string so that other functionality still works - # also save sizes - symbol_str = "[" - for child, size in zip(all_child_vectors, all_child_sizes): - symbol_str += "{}::{}, ".format(size, child) - symbol_str = symbol_str[:-2] + "]" - + raise NotImplementedError("Step has to be an integer.") else: - # A regular Concatenation for the MTK model - # We will define the concatenation function separately - symbol_str = "concatenation(" + ", ".join(children_vars) + ")" - - # Note: we assume that y is being passed as a column vector - elif isinstance(symbol, pybamm.StateVectorBase): - if isinstance(symbol, pybamm.StateVector): - name = "@view y" - elif isinstance(symbol, pybamm.StateVectorDot): - name = "@view dy" - indices = np.argwhere(symbol.evaluation_array).reshape(-1).astype(np.int32) - # add 1 since julia uses 1-indexing - indices += 1 - if len(indices) == 1: - symbol_str = "{}[{}]".format(name, indices[0]) - else: - # julia does include the final value - symbol_str = "{}[{}:{}]".format(name, indices[0], indices[-1]) - - elif isinstance(symbol, pybamm.Time): - symbol_str = "t" - - elif isinstance(symbol, pybamm.InputParameter): - symbol_str = "inputs['{}']".format(symbol.name) - - elif isinstance(symbol, pybamm.SpatialVariable): - symbol_str = symbol.name + raise NotImplementedError("Step must be a slice or an int") + + #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. + def break_down_binary(self,symbol): + #Check for constant + #assert not is_constant_and_can_evaluate(symbol) + + #We know that this should only have 2 children + assert len(symbol.children)==2 + + #take care of the kids first (this is recursive but multiple-dispatch recursive which is cool) + id_left = self.convert_tree_to_intermediate(symbol.children[0]) + id_right = self.convert_tree_to_intermediate(symbol.children[1]) + my_id = symbol.id + return id_left,id_right,my_id + + def break_down_concatenation(self,symbol): + child_ids = [] + for child in symbol.children: + child_id = self.convert_tree_to_intermediate(child) + child_ids.append(child_id) + first_id = child_ids[0] + num_cols = self._intermediate[first_id].shape[1] + num_rows = 0 + for child_id in child_ids: + child_shape = self._intermediate[child_id].shape + assert num_cols == child_shape[1] + num_rows+=child_shape[0] + shape = (num_rows,num_cols) + return child_ids,shape - elif isinstance(symbol, pybamm.FunctionParameter): - symbol_str = "{}({})".format(symbol.name, ", ".join(children_vars)) + + #Convert-Trees go here + + #Binary trees constructors. All follow the pattern of mat-mul. They need to find their shapes, assuming that the shapes of the nodes one level below them in the expression tree have already been computed. + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.NumpyConcatenation): + my_id = symbol.id + children_julia,shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaNumpyConcatenation(my_id,shape,children_julia) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.SparseStack): + my_id = symbol.id + children_julia,shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaSparseStack(my_id,shape,children_julia) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.DomainConcatenation): + my_id = symbol.id + children_julia,shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaDomainConcatenation(my_id,shape,children_julia,symbol.secondary_dimensions_npts,symbol._children_slices) + return my_id + + + @multimethod + def convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): + #Break down the binary tree + id_left,id_right,my_id = self.break_down_binary(symbol) + left_shape = self._intermediate[id_left].shape + right_shape = self._intermediate[id_right].shape + my_shape = (left_shape[0],right_shape[1]) + #Cache the result. + self._intermediate[my_id] = JuliaMatrixMultiplication(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) + return my_id + + #Apparently an inner product is a hadamard product in pybamm + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Inner): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Division): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaDivision(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol: pybamm.Addition): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Minimum): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min") + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Maximum): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max") + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Power): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.EqualHeaviside): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<=") + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.NotEqualHeaviside): + id_left,id_right,my_id = self.break_down_binary(symbol) + my_shape = self.find_the_nonscalar(id_left,id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<") + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Index): + assert len(symbol.children)==1 + id_lower = self.convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + index = symbol.index + self._intermediate[my_id] = JuliaIndex(id_lower,my_id,index) + return my_id + + + + #Convenience function for operations which can have + def find_the_nonscalar(self,id_left,id_right): + left_type = type(self._intermediate[id_left]) + right_type = type(self._intermediate[id_right]) + if issubclass(left_type,JuliaScalar): + return self._intermediate[id_right].shape + elif issubclass(right_type,JuliaScalar): + return self._intermediate[id_left].shape + else: + return self.same_shape(id_left,id_right) + + #to find the shape, there are a number of elements that should just have the shame shape as their children. This function removes boilerplate by implementing those cases + def same_shape(self,id_left,id_right): + left_shape = self._intermediate[id_left].shape + right_shape = self._intermediate[id_right].shape + assert left_shape==right_shape + return left_shape + - else: - raise NotImplementedError( + #Functions + #Broadcastable functions have 1 input and 1 output, and the input and output have the same shape. The hard part is that we have to know which is which and pybamm doesn't differentiate between the two. So, we have to do that with an if statement. + @multimethod + def convert_tree_to_intermediate(self,symbol): + raise NotImplementedError( "Conversion to Julia not implemented for a symbol of type '{}'".format( type(symbol) ) ) - variable_symbols[symbol.id] = symbol_str - - # Save the size of the symbol - try: - if symbol.shape == (): - variable_symbol_sizes[symbol.id] = 1 + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Min): + my_jl_name = symbol.julia_name + assert len(symbol.children)==1 + my_shape = (1,1) + input = self.convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaMinimumMaximum(my_jl_name,input,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Max): + my_jl_name = symbol.julia_name + assert len(symbol.children)==1 + my_shape = (1,1) + input = self.convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaMinimumMaximum(my_jl_name,input,my_id,my_shape) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Function): + my_jl_name = symbol.julia_name + assert len(symbol.children)==1 + my_shape = symbol.children[0].shape + input = self.convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaBroadcastableFunction(my_jl_name,input,my_id,my_shape) + return my_id + + + + #Constants and Values. There are only 2 of these. They must know their own shapes. + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Matrix): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + if value.shape==(1,1): + self._intermediate[my_id] = JuliaScalar(my_id,value) else: - variable_symbol_sizes[symbol.id] = symbol.shape[0] - except NotImplementedError: - pass - - -def to_julia(symbol, round_constants=True): - """ - This function converts an expression tree into a dict of constant input values, and - valid julia code that acts like the tree's :func:`pybamm.Symbol.evaluate` function - - Parameters - ---------- - symbol : :class:`pybamm.Symbol` - The symbol to convert to julia code - - Returns - ------- - constant_values : collections.OrderedDict - dict mapping node id to a constant value. Represents all the constant nodes in - the expression tree - str - valid julia code that will evaluate all the variable nodes in the tree. - - """ - - constant_values = OrderedDict() - variable_symbols = OrderedDict() - variable_symbol_sizes = OrderedDict() - find_symbols( - symbol, - constant_values, - variable_symbols, - variable_symbol_sizes, - round_constants=round_constants, - ) - - return constant_values, variable_symbols, variable_symbol_sizes - - -def get_julia_function( - symbol, - funcname="f", - input_parameter_order=None, - len_rhs=None, - preallocate=True, - round_constants=True, - cache_type="standard" -): - """ - Converts a pybamm expression tree into pure julia code that will calculate the - result of calling `evaluate(t, y)` on the given expression tree. - - Parameters - ---------- - symbol : :class:`pybamm.Symbol` - The symbol to convert to julia code - funcname : str, optional - The name to give to the function (default 'f') - input_parameter_order : list, optional - List of input parameter names. Defines the order in which the input parameters - are extracted from 'p' in the julia function that is created - len_rhs : int, optional - The number of ODEs in the discretized differential equations. This also - determines whether the model has any algebraic equations: if None (default), - the model is assume to have no algebraic parts and ``julia_str`` is compatible - with an ODE solver. If not None, ``julia_str`` is compatible with a DAE solver - preallocate : bool, optional - Whether to write the function in a way that preallocates memory for the output. - Default is True, which is faster. Must be False for the function to be - modelingtoolkitized. - cache_type : str, optional - The type of cache to use for the function. Must be one of 'standard', 'dual', 'symbolic', - or 'gpu'. If 'standard', the function will be cached in the standard way, - If 'dual', the function will use the dualcache provided by preallocationtools.jl, - and if 'symbolic', the function will use the symcache provided by PyBaMM.jl. Default - is standard, and as of so far, I haven't been able to beat it with performance yet. - - Returns - ------- - julia_str : str - String of julia code, to be evaluated by ``julia.Main.eval`` - - """ - if len_rhs is None: - typ = "ode" - else: - typ = "dae" - # Take away dy from the differential states - # we will return a function of the form - # out[] = .. - dy[] for the differential states - # out[] = .. for the algebraic states - symbol_minus_dy = [] - end = 0 - for child in symbol.orphans: - start = end - end += child.size - if end <= len_rhs: - symbol_minus_dy.append(child - pybamm.StateVectorDot(slice(start, end))) - else: - symbol_minus_dy.append(child) - symbol = pybamm.numpy_concatenation(*symbol_minus_dy) - constants, var_symbols, var_symbol_sizes = to_julia( - symbol, round_constants=round_constants - ) - - # extract constants in generated function - const_and_cache_str = "cs = (\n" - shorter_const_names = {} - for i_const, (symbol_id, const_value) in enumerate(constants.items()): - const_name = id_to_julia_variable(symbol_id, "const") - const_name_short = "const_{}".format(i_const) - if cache_type=="gpu": - const_and_cache_str += " {} = cu({}),\n".format(const_name_short, const_value) + self._intermediate[my_id] = JuliaConstant(my_id,value) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Vector): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + if value.shape==(1,1): + self._intermediate[my_id] = JuliaScalar(my_id,value) else: - const_and_cache_str += " {} = {},\n".format(const_name_short, const_value) - shorter_const_names[const_name] = const_name_short + self._intermediate[my_id] = JuliaConstant(my_id,value) + return my_id - # Pop (get and remove) items from the dictionary of symbols one by one - # If they are simple operations (@view, +, -, *, /), replace all future - # occurences instead of assigning them. This "inlining" speeds up the computation - inlineable_symbols = ["@view", "+", "-", "*", "/"] - var_str = "" - input_parameters = {} - while var_symbols: - var_symbol_id, symbol_line = var_symbols.popitem(last=False) - julia_var = id_to_julia_variable(var_symbol_id, "cache") - # Look for lists in the variable symbols. These correpsond to concatenations, so - # assign the children to the right parts of the vector - #symbol_line_split = symbol_line.split(", ") - #var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) - if symbol_line[0] == "[" and symbol_line[-1] == "]": - # convert to actual list - symbol_line = symbol_line[1:-1].split(", ") - start = 0 - if preallocate is True or var_symbol_id == symbol.id: - for child_size_and_name in symbol_line: - child_size, child_name = child_size_and_name.split("::") - end = start + int(child_size) - # add 1 to start to account for julia 1-indexing - var_str += "@. {}[{}:{}] = {}\n".format( - julia_var, start + 1, end, child_name - ) - start = end - else: - concat_str = "{} = vcat(".format(julia_var) - for i, child_size_and_name in enumerate(symbol_line): - child_size, child_name = child_size_and_name.split("::") - var_str += "x{} = @. {}\n".format(i + 1, child_name) - concat_str += "x{}, ".format(i + 1) - var_str += concat_str[:-2] + ")\n" - # use mul! for matrix multiplications (requires LinearAlgebra library) - elif " @ " in symbol_line: - if preallocate is False: - symbol_line = symbol_line.replace(" @ ", " * ") - var_str += "{} = {}\n".format(julia_var, symbol_line) - else: - symbol_line = symbol_line.replace(" @ ", ", ") - var_str += "mul!({}, {})\n".format(julia_var, symbol_line) - # find input parameters - elif symbol_line.startswith("inputs"): - input_parameters[julia_var] = symbol_line[8:-2] - elif "minimum" in symbol_line or "maximum" in symbol_line: - var_str += "{} .= {}\n".format(julia_var, symbol_line) + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Scalar): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + self._intermediate[my_id] = JuliaScalar(my_id,value) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Time): + my_id = symbol.id + self._intermediate[my_id] = JuliaTime(my_id) + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.InputParameter): + my_id = symbol.id + name = symbol.name + self._intermediate[my_id] = JuliaInput(my_id,name) + self._parameter_dict[my_id] = name + return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.StateVector): + my_id = symbol.id + first_point = symbol.first_point + last_point = symbol.last_point + points = (first_point,last_point) + shape = symbol.shape + self._intermediate[my_id] = JuliaStateVector(id,points,shape) + return my_id + + #utilities for code conversion + def get_variables_for_binary_tree(self,julia_symbol): + left_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.left_input]) + right_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.right_input]) + result_var_name = self.get_result_variable_name(julia_symbol) + return left_input_var_name,right_input_var_name,result_var_name + + #convert intermediates to code. Again, all binary trees follow the same pattern so we just define a function to break them down, and then use the MD to find out what code to generate. + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaConcatenation): + input_var_names = [] + num_cols = julia_symbol.shape[1] + my_name = self.get_result_variable_name(julia_symbol) + + #assume we don't have tensors. Already asserted that concatenations have to have the same width. + if num_cols==1: + right_parenthesis = "]" + vec=True else: - # don't replace the matrix multiplication cases (which will be - # turned into a mul!), since it is faster to assign to a cache array - # first in that case - # e.g. mul!(cs.cache_1, cs.cache_2, cs.cache_3) - # unless it is a @view in which case we don't - # need to cache - # e.g. mul!(cs.cache_1, cs.cache_2, @view y[1:10]) - # also don't replace the minimum() or maximum() cases as we can't - # broadcast them - any_matmul_min_max = any( - julia_var in next_symbol_line - and ( - any( - x in next_symbol_line - for x in [" @ ", "mul!", "minimum", "maximum"] - ) - and not symbol_line.startswith("@view") - ) - for next_symbol_line in var_symbols.values() - ) - # inline operation if it can be inlined - if ( - any(x in symbol_line for x in inlineable_symbols) or symbol_line == "t" - ) and not any_matmul_min_max: - found_replacement = False - # replace all other occurrences of the variable - # in the dictionary with the symbol line - for next_var_id, next_symbol_line in var_symbols.items(): - if julia_var in next_symbol_line: - if symbol_line == "t": - # no brackets needed - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, symbol_line - ) - else: - # add brackets so that the order of operations is maintained - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, "({})".format(symbol_line) - ) - found_replacement = True - if not found_replacement: - var_str += "@. {} = {}\n".format(julia_var, symbol_line) - - # otherwise assign + right_parenthesis = ",:]" + vec=False + #do the 0th one outside of the loop to initialize + child = julia_symbol.children[0] + child_var = self._intermediate[child] + child_var_name = self.get_result_variable_name(self._intermediate[child]) + start_row = 1 + if child_var.shape[0] == 0: + end_row = 1 + code = "" + elif child_var.shape[0] == 1: + end_row = 1 + if vec: + code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: - var_str += "@. {} = {}\n".format(julia_var, symbol_line) - # Replace all input parameter names - for input_parameter_id, input_parameter_name in input_parameters.items(): - var_str = var_str.replace(input_parameter_id, input_parameter_name) - - # indent code - var_str = " " + var_str - var_str = var_str.replace("\n", "\n ") - - - cache_initialization_str = "" - - # add the cache variables to the cache NamedTuple - i_cache = 0 - for var_symbol_id, var_symbol_size in var_symbol_sizes.items(): - # Skip caching the result variable since this is provided as dy - # Also skip caching the result variable if it doesn't appear in the var_str, - # since it has been inlined and does not need to be assigned to - julia_var = id_to_julia_variable(var_symbol_id, "cache") - if var_symbol_id != symbol.id and julia_var in var_str: - julia_var_short = "cache_{}".format(i_cache) - var_str = var_str.replace(julia_var, julia_var_short) - i_cache += 1 - if preallocate is True: - if cache_type == "symbolic": - const_and_cache_str += " {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format( - julia_var_short, var_symbol_size,var_symbol_size - ) - cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) - elif cache_type == "standard": - const_and_cache_str += " {} = zeros({}),\n".format( - julia_var_short, var_symbol_size - ) - elif cache_type == "dual": - const_and_cache_str += " {} = dualcache(zeros({}),12),\n".format( - julia_var_short, var_symbol_size - ) - cache_initialization_str += " {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) - elif cache_type == "gpu": - const_and_cache_str+=" {} = CUDA.zeros({}),\n".format(julia_var_short,var_symbol_size) - - else: - # Cache variables have not been preallocated - var_str = var_str.replace( - "@. {} = ".format(julia_var_short), - "{} = @. ".format(julia_var_short), - ) - - # Shorten the name of the constants from id to const_0, const_1, etc. - for long, short in shorter_const_names.items(): - var_str = var_str.replace(long, "cs." + short) - - # close the constants and cache string - const_and_cache_str += ")\n" - - # remove the constant and cache sring if it is empty - const_and_cache_str = const_and_cache_str.replace("cs = (\n)\n", "") - - # calculate the final variable that will output the result - if symbol.is_constant(): - result_var = id_to_julia_variable(symbol.id, "const") - if result_var in shorter_const_names: - result_var = shorter_const_names[result_var] - result_value = symbol.evaluate() - if isinstance(result_value, numbers.Number): - var_str = var_str + "\n dy .= " + str(result_value) + "\n" + code = "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: - var_str = var_str + "\n dy .= cs." + result_var + "\n" - else: - result_var = id_to_julia_variable(symbol.id, "cache") - if typ == "ode": - out = "dy" - elif typ == "dae": - out = "out" - # replace "cache_123 = ..." with "dy .= ..." (ensure we allocate to the - # variable that was passed in) - var_str = var_str.replace(f" {result_var} =", f" {out} .=") - # catch other cases for dy - var_str = var_str.replace(result_var, out) - - # add "cs." to cache names - if preallocate is True: - if cache_type in ["standard","gpu"]: - var_str = var_str.replace("cache", "cs.cache") - - # line that extracts the input parameters in the right order - if input_parameter_order is None: - input_parameter_extraction = "" - elif len(input_parameter_order) == 1: - # extract the single parameter - input_parameter_extraction = " " + input_parameter_order[0] + " = p[1]\n" - else: - # extract all parameters - input_parameter_extraction = " " + ", ".join(input_parameter_order) + " = p\n" - - if preallocate is False or const_and_cache_str == "": - func_def = f"{funcname}!" - else: - func_def = f"{funcname}_with_consts!" - - # add function def - if typ == "ode": - function_def = f"\nfunction {func_def}(dy, y, p, t)\n" - elif typ == "dae": - function_def = f"\nfunction {func_def}(out, dy, y, p, t)\n" - julia_str = ( - "begin\n" - + const_and_cache_str - + function_def - + cache_initialization_str - + input_parameter_extraction - + var_str - ) - - # close the function, with a 'nothing' to avoid allocations - julia_str += "nothing\nend\n\n" - julia_str = julia_str.replace("\n \n", "\n") - - if not (preallocate is False or const_and_cache_str == ""): - # Use a let block for the cached variables - # open the let block - julia_str = julia_str.replace("cs = (", f"{funcname}! = let cs = (") - # close the let block - julia_str += "end\n" - - # close the "begin" - julia_str += "end" - - return julia_str - - -def convert_var_and_eqn_to_str(var, eqn, all_constants_str, all_variables_str, typ): - """ - Converts a variable and its equation to a julia string - - Parameters - ---------- - var : :class:`pybamm.Symbol` - The variable (key in the dictionary of rhs/algebraic/initial conditions) - eqn : :class:`pybamm.Symbol` - The equation (value in the dictionary of rhs/algebraic/initial conditions) - all_constants_str : str - String containing all the constants defined so far - all_variables_str : str - String containing all the variables defined so far - typ : str - The type of the variable/equation pair being converted ("equation", "initial - condition", or "boundary condition") - - Returns - ------- - all_constants_str : str - Updated string of all constants - all_variables_str : str - Updated string of all variables - eqn_str : str - The string describing the final equation result, perhaps as a function of some - variables and/or constants in all_constants_str and all_variables_str - - """ - if isinstance(eqn, pybamm.Broadcast): - # ignore broadcasts for now - eqn = eqn.child - - var_symbols = to_julia(eqn)[1] - - # var_str = "" - # for symbol_id, symbol_line in var_symbols.items(): - # var_str += f"{id_to_julia_variable(symbol_id)} = {symbol_line}\n" - # Pop (get and remove) items from the dictionary of symbols one by one - # If they are simple operations (+, -, *, /), replace all future - # occurences instead of assigning them. - inlineable_symbols = [" + ", " - ", " * ", " / "] - var_str = "" - while var_symbols: - var_symbol_id, symbol_line = var_symbols.popitem(last=False) - julia_var = id_to_julia_variable(var_symbol_id, "cache") - # inline operation if it can be inlined - if "concatenation" not in symbol_line: - found_replacement = False - # replace all other occurrences of the variable - # in the dictionary with the symbol line - for next_var_id, next_symbol_line in var_symbols.items(): - if ( - symbol_line == "t" - or " " not in symbol_line - or symbol_line.startswith("grad") - or not any(x in next_symbol_line for x in inlineable_symbols) - ): - # cases that don't need brackets - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, symbol_line - ) - elif next_symbol_line.startswith("concatenation"): - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, f"\n {symbol_line}\n" - ) + start_row = 1 + end_row = child_var.shape[0] + code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + + for child in julia_symbol.children[1:]: + child_var = self._intermediate[child] + child_var_name = self.get_result_variable_name(self._intermediate[child]) + if child_var.shape[0] == 0: + continue + elif child_var.shape[0] == 1: + start_row = end_row+1 + end_row = start_row+1 + if vec: + code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: - # add brackets so that the order of operations is maintained - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, "({})".format(symbol_line) - ) - found_replacement = True - if not found_replacement: - var_str += "{} = {}\n".format(julia_var, symbol_line) - - # otherwise assign + code += "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + start_row = end_row+1 + end_row = start_row+child_var.shape[0]-1 + code += "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaDomainConcatenation): + input_var_names = [] + num_cols = julia_symbol.shape[1] + my_name = self.get_result_variable_name(julia_symbol) + + #assume we don't have tensors. Already asserted that concatenations have to have the same width. + if num_cols==1: + right_parenthesis = "]" + vec=True else: - var_str += "{} = {}\n".format(julia_var, symbol_line) - - # If we have created a concatenation we need to define it - # Hardcoded to the negative electrode, separator, positive electrode case for now - if "concatenation" in var_str and "function concatenation" not in all_variables_str: - concatenation_def = ( - "\nfunction concatenation(n, s, p)\n" - + " # A concatenation in the electrolyte domain\n" - + " IfElse.ifelse(\n" - + " x < neg_width, n, IfElse.ifelse(\n" - + " x < neg_plus_sep_width, s, p\n" - + " )\n" - + " )\n" - + "end\n" - ) - else: - concatenation_def = "" - - # Define the FunctionParameter objects that have not yet been defined - function_defs = "" - for x in eqn.pre_order(): - if ( - isinstance(x, pybamm.FunctionParameter) - and f"function {x.name}" not in all_variables_str - and typ == "equation" - ): - function_def = ( - f"\nfunction {x.name}(" - + ", ".join(x.arg_names) - + ")\n" - + " {}\n".format(str(x.callable).replace("**", "^")) - + "end\n" - ) - function_defs += function_def - - if concatenation_def + function_defs != "": - function_defs += "\n" - - var_str = concatenation_def + function_defs + var_str - - # add a comment labeling the equation, and the equation itself - if var_str == "": - all_variables_str += "" - else: - all_variables_str += f"# '{var.name}' {typ}\n" + var_str + "\n" - - # calculate the final variable that will output the result - if eqn.is_constant(): - result_var = id_to_julia_variable(eqn.id, "const") - else: - result_var = id_to_julia_variable(eqn.id, "cache") - if is_constant_and_can_evaluate(eqn): - result_value = eqn.evaluate() - else: - result_value = None - - # define the variable that goes into the equation - if eqn.is_constant() and isinstance(result_value, numbers.Number): - eqn_str = str(result_value) - else: - eqn_str = result_var - - return all_constants_str, all_variables_str, eqn_str + right_parenthesis = ",:]" + vec=False + #do the 0th one outside of the loop to initialize + end_row = 0 + code = "" + for i in range(julia_symbol.secondary_dimension_npts): + for c in range(len(julia_symbol.children)): + child_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.children[c]]) + this_slice = list(julia_symbol.children_slices[c].values())[0][i] + start = this_slice.start + stop = this_slice.stop + start_row = end_row+1 + end_row = start_row+(stop-start)-1 + code += "{}[{}:{}{} .= {}[{}:{}{}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + + self._function_string+=code + return 0 -def get_julia_mtk_model(model, geometry=None, tspan=None): - """ - Converts a pybamm model into a Julia ModelingToolkit model - - Parameters - ---------- - model : :class:`pybamm.BaseModel` - The model to be converted - geometry : dict, optional - Dictionary defining the geometry. Must be provided if the model is a PDE model - tspan : array-like, optional - Time for which to solve the model. Must be provided if the model is a PDE model - - Returns - ------- - mtk_str : str - String of julia code representing a model in MTK, - to be evaluated by ``julia.Main.eval`` - """ - # Extract variables - variables = {**model.rhs, **model.algebraic}.keys() - variable_to_print_name = {} - for i, var in enumerate(variables): - if var.print_name is not None: - print_name = var._raw_print_name + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - print_name = f"u{i+1}" - variable_to_print_name[var] = print_name - if isinstance(var, pybamm.ConcatenationVariable): - for child in var.children: - variable_to_print_name[child] = print_name - - # Extract domain and auxiliary domains - all_domains = set( - [tuple(dom) for var in variables for dom in var.domains.values() if dom != []] - ) - is_pde = bool(all_domains) - - # Check geometry and tspan have been provided if a PDE - if is_pde: - if geometry is None: - raise ValueError("must provide geometry if the model is a PDE model") - if tspan is None: - raise ValueError("must provide tspan if the model is a PDE model") - - # Read domain names - domain_name_to_symbol = {} - long_domain_symbol_to_short = {} - for dom in all_domains: - # Read domain name from geometry - domain_symbol = list(geometry[dom[0]].keys())[0] - if len(dom) > 1: - domain_symbol = domain_symbol[0] - # For multi-domain variables keep only the first letter of the domain - domain_name_to_symbol[tuple(dom)] = domain_symbol - # Record which domain symbols we shortened - for d in dom: - long = list(geometry[d].keys())[0] - long_domain_symbol_to_short[long] = domain_symbol + code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - # Otherwise keep the whole domain - domain_name_to_symbol[tuple(dom)] = domain_symbol - - # Read domain limits - domain_name_to_limits = {(): None} - for dom in all_domains: - limits = list(geometry[dom[0]].values())[0].values() - if len(limits) > 1: - lower_limit, _ = list(geometry[dom[0]].values())[0].values() - _, upper_limit = list(geometry[dom[-1]].values())[0].values() - domain_name_to_limits[tuple(dom)] = ( - lower_limit.evaluate(), - upper_limit.evaluate(), - ) + code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - # Don't record limits for variables that have "limits" of length 1 i.e. - # a zero-dimensional domain - domain_name_to_limits[tuple(dom)] = None - - # Define independent variables for each variable - var_to_ind_vars = {} - var_to_ind_vars_left_boundary = {} - var_to_ind_vars_right_boundary = {} - for var in variables: - if var.domain in [[], ["current collector"]]: - var_to_ind_vars[var] = "(t)" + code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - # all independent variables e.g. (t, x) or (t, rn, xn) - domain_symbols = ", ".join( - domain_name_to_symbol[tuple(dom)] - for dom in var.domains.values() - if domain_name_to_limits[tuple(dom)] is not None - ) - var_to_ind_vars[var] = f"(t, {domain_symbols})" - if isinstance(var, pybamm.ConcatenationVariable): - for child in var.children: - var_to_ind_vars[child] = f"(t, {domain_symbols})" - aux_domain_symbols = ", ".join( - domain_name_to_symbol[tuple(dom)] - for level, dom in var.domains.items() - if level != "primary" and domain_name_to_limits[tuple(dom)] is not None - ) - if aux_domain_symbols != "": - aux_domain_symbols = ", " + aux_domain_symbols - - limits = domain_name_to_limits[tuple(var.domain)] - # left bc e.g. (t, 0) or (t, 0, xn) - var_to_ind_vars_left_boundary[var] = f"(t, {limits[0]}{aux_domain_symbols})" - # right bc e.g. (t, 1) or (t, 1, xn) - var_to_ind_vars_right_boundary[ - var - ] = f"(t, {limits[1]}{aux_domain_symbols})" - - mtk_str = "begin\n" - # Define parameters (including independent variables) - # Makes a line of the form '@parameters t x1 x2 x3 a b c d' - ind_vars = ["t"] + [ - sym - for dom, sym in domain_name_to_symbol.items() - if domain_name_to_limits[dom] is not None - ] - for domain_name, domain_symbol in domain_name_to_symbol.items(): - if domain_name_to_limits[domain_name] is not None: - mtk_str += f"# {domain_name} -> {domain_symbol}\n" - mtk_str += "@parameters " + " ".join(ind_vars) - if len(model.input_parameters) > 0: - mtk_str += "\n# Input parameters\n@parameters" - for param in model.input_parameters: - mtk_str += f" {param.name}" - mtk_str += "\n" - - # Add a comment with the variable names - for var in variables: - mtk_str += f"# '{var.name}' -> {variable_to_print_name[var]}\n" - # Makes a line of the form '@variables u1(t) u2(t)' - dep_vars = [] - mtk_str += "@variables" - for var in variables: - mtk_str += f" {variable_to_print_name[var]}(..)" - dep_var = variable_to_print_name[var] + var_to_ind_vars[var] - dep_vars.append(dep_var) - mtk_str += "\n" - - # Define derivatives - for domain_symbol in ind_vars: - mtk_str += f"D{domain_symbol} = Differential({domain_symbol})\n" - mtk_str += "\n" - - # Define equations - all_eqns_str = "" - all_constants_str = "" - all_julia_str = "" - for var, eqn in {**model.rhs, **model.algebraic}.items(): - all_constants_str, all_julia_str, eqn_str = convert_var_and_eqn_to_str( - var, eqn, all_constants_str, all_julia_str, "equation" - ) + code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): + result_var_name = self.get_result_variable_name(julia_symbol) + input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaMinimumMaximum): + result_var_name = self.get_result_variable_name(julia_symbol) + input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + code = "{} .= {}({})\n".format(result_var_name,julia_symbol.name,input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaMinMax): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) + else: + code = "{} = {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaPower): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {}.^{}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {}.^{})\n".format(result_var_name,left_input_var_name,right_input_var_name) + self._function_string+=code + return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaBitwiseBinaryOperation): + left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if self._preallocate: + code = "{} .= {}.{}{}\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) + else: + code = "{} = {}.{}{})\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) + self._function_string+=code + return 0 - if var in model.rhs: - all_eqns_str += ( - f" Dt({variable_to_print_name[var]}{var_to_ind_vars[var]}) " - + f"~ {eqn_str},\n" - ) - elif var in model.algebraic: - all_eqns_str += f" 0 ~ {eqn_str},\n" - - # Replace any long domain symbols with the short version - # e.g. "xn" gets replaced with "x" - for long, short in long_domain_symbol_to_short.items(): - # we need to add a space to avoid accidentally replacing 'exp' with 'ex' - all_julia_str = all_julia_str.replace(" " + long, " " + short) - - # Replace variables in the julia strings that correspond to pybamm variables with - # their julia equivalent - for var, julia_id in variable_to_print_name.items(): - # e.g. boundary_value_right(cache_123456789) gets replaced with u1(t, 1) - cache_var_id = id_to_julia_variable(var.id, "cache") - if f"boundary_value_right({cache_var_id})" in all_julia_str: - all_julia_str = all_julia_str.replace( - f"boundary_value_right({cache_var_id})", - julia_id + var_to_ind_vars_right_boundary[var], - ) - # e.g. cache_123456789 gets replaced with u1(t, x) - all_julia_str = all_julia_str.replace( - cache_var_id, julia_id + var_to_ind_vars[var] - ) + #Cache and Const Creation + @multimethod + def create_cache(self,symbol): + my_id = symbol.output - # Replace independent variables (domain names) in julia strings with the - # corresponding symbol - for domain_name, domain_symbol in domain_name_to_symbol.items(): - all_julia_str = all_julia_str.replace( - f"grad_{domain_name}", f"D{domain_symbol}" - ) - # Different divergence depending on the coordinate system - coord_sys = getattr(pybamm.standard_spatial_vars, domain_symbol).coord_sys - if coord_sys == "cartesian": - all_julia_str = all_julia_str.replace( - f"div_{domain_name}", f"D{domain_symbol}" - ) - elif coord_sys == "spherical polar": - all_julia_str = all_julia_str.replace( - f"div_{domain_name}(", - f"1 / {domain_symbol}^2 * D{domain_symbol}({domain_symbol}^2 * ", - ) + cache_shape = self._intermediate[my_id].shape - # Replace the thicknesses in the concatenation with the actual thickness from the - # geometry - if "neg_width" in all_julia_str or "neg_plus_sep_width" in all_julia_str: - var = pybamm.standard_spatial_vars - x_n = geometry["negative electrode"]["x_n"]["max"].evaluate() - x_s = geometry["separator"]["x_s"]["max"].evaluate() - all_julia_str = all_julia_str.replace("neg_width", str(x_n)) - all_julia_str = all_julia_str.replace("neg_plus_sep_width", str(x_s)) - - # Update the MTK string - mtk_str += all_constants_str + all_julia_str + "\n" + f"eqs = [\n{all_eqns_str}]\n" - - #################################################################################### - # Initial and boundary conditions - #################################################################################### - # Initial conditions - all_ic_bc_str = " # initial conditions\n" - all_ic_bc_constants_str = "" - all_ic_bc_julia_str = "" - for var, eqn in model.initial_conditions.items(): - ( - all_ic_bc_constants_str, - all_ic_bc_julia_str, - eqn_str, - ) = convert_var_and_eqn_to_str( - var, eqn, all_ic_bc_constants_str, all_ic_bc_julia_str, "initial condition" - ) + cache_id = self._cache_id+1 + self._cache_id = cache_id + + cache_name = "cache_{}".format(cache_id) + self._cache_dict[symbol.output] = "cs."+cache_name - if not is_pde: - all_ic_bc_str += f" {variable_to_print_name[var]}(t) => {eqn_str},\n" + if self._cache_type=="standard": + if cache_shape[1] == 1: + cache_shape = "({})".format(cache_shape[0]) + self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) + return 0 else: - if var.domain == []: - doms = "" - else: - doms = ", " + domain_name_to_symbol[tuple(var.domain)] - - all_ic_bc_str += f" {variable_to_print_name[var]}(0{doms}) ~ {eqn_str},\n" - # Boundary conditions - if is_pde: - all_ic_bc_str += " # boundary conditions\n" - for var, eqn_side in model.boundary_conditions.items(): - if isinstance(var, (pybamm.Variable, pybamm.ConcatenationVariable)): - for side, (eqn, typ) in eqn_side.items(): - ( - all_ic_bc_constants_str, - all_ic_bc_julia_str, - eqn_str, - ) = convert_var_and_eqn_to_str( - var, - eqn, - all_ic_bc_constants_str, - all_ic_bc_julia_str, - "boundary condition", - ) - - if side == "left": - limit = var_to_ind_vars_left_boundary[var] - elif side == "right": - limit = var_to_ind_vars_right_boundary[var] - - bc = f"{variable_to_print_name[var]}{limit}" - if typ == "Dirichlet": - bc = bc - elif typ == "Neumann": - bc = f"D{domain_name_to_symbol[tuple(var.domain)]}({bc})" - all_ic_bc_str += f" {bc} ~ {eqn_str},\n" - - # Replace variables in the julia strings that correspond to pybamm variables with - # their julia equivalent - for var, julia_id in variable_to_print_name.items(): - # e.g. boundary_value_right(cache_123456789) gets replaced with u1(t, 1) - cache_var_id = id_to_julia_variable(var.id, "cache") - if f"boundary_value_right({cache_var_id})" in all_ic_bc_julia_str: - all_ic_bc_julia_str = all_ic_bc_julia_str.replace( - f"boundary_value_right({cache_var_id})", - julia_id + var_to_ind_vars_right_boundary[var], - ) - # e.g. cache_123456789 gets replaced with u1(t, x) - all_ic_bc_julia_str = all_ic_bc_julia_str.replace( - cache_var_id, julia_id + var_to_ind_vars[var] - ) + raise NotImplementedError("uh oh") - #################################################################################### + - # Create ODESystem or PDESystem - if not is_pde: - mtk_str += "sys = ODESystem(eqs, t)\n\n" - mtk_str += ( - all_ic_bc_constants_str - + all_ic_bc_julia_str - + "\n" - + f"u0 = [\n{all_ic_bc_str}]\n" - ) - else: - # Initial and boundary conditions - mtk_str += ( - all_ic_bc_constants_str - + all_ic_bc_julia_str - + "\n" - + f"ics_bcs = [\n{all_ic_bc_str}]\n" + def create_const(self,symbol): + my_id = symbol.id + const_id = self._const_id+1 + self._const_id = const_id + const_name = "const_{}".format(const_id) + self._const_dict[my_id] = "cs."+const_name + mat_value = symbol.value + val_line = self.write_const(mat_value) + const_line = const_name+" = {},\n".format(val_line) + self._cache_and_const_string+=const_line + return 0 + + @multimethod + def write_const(self,mat_value:numpy.ndarray): + return mat_value + + @multimethod + def write_const(self,value:scipy.sparse._csr.csr_matrix): + row, col, data = scipy.sparse.find(value) + m, n = value.shape + np.set_printoptions( + threshold=max(np.get_printoptions()["threshold"], len(row) + 10) ) - # Domains - mtk_str += "\n" - tpsan_str = ",".join( - map(lambda x: f"{x / model.timescale.evaluate():.3f}", tspan) - ) - mtk_str += f"t_domain = Interval({tpsan_str})\n" - domains = "domains = [\n t in t_domain,\n" - for domain, symbol in domain_name_to_symbol.items(): - limits = domain_name_to_limits[tuple(domain)] - if limits is not None: - mtk_str += f"{symbol}_domain = Interval{limits}\n" - domains += f" {symbol} in {symbol}_domain,\n" - domains += "]\n" - - mtk_str += "\n" - mtk_str += domains - - # Independent and dependent variables - mtk_str += "ind_vars = [{}]\n".format(", ".join(ind_vars)) - mtk_str += "dep_vars = [{}]\n\n".format(", ".join(dep_vars)) - - name = model.name.replace(" ", "_").replace("-", "_") - mtk_str += ( - name - + "_pde_system = PDESystem(eqs, ics_bcs, domains, ind_vars, dep_vars)\n\n" - ) + val_string = "sparse({}, {}, {}, {}, {})".format( + np.array2string(row + 1, separator=","), + np.array2string(col + 1, separator=","), + np.array2string(data, separator=","), + m, + n, + ) + return val_string + + #Just get something working here, so can start actual testing + def write_function_easy(self,funcname): + #start with the closure + self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string + self._cache_and_const_string += ")\n" + + + top = self._intermediate[next(reversed(self._intermediate))] + top_var_name = self.get_result_variable_name(top) + my_shape = top.shape + if len(self._parameter_dict) != 0: + parameter_string = "" + for parameter in self._parameter_dict.items(): + parameter_string+="{},".format(parameter[1]) + parameter_string = parameter_string[0:-1] + parameter_string += "= p\n" + self._function_string = parameter_string + self._function_string + if my_shape[1] != 1: + self._function_string += "J[:,:] .= {}\nend\nend".format(top_var_name) + self._function_string = "function {}(J,y,p,t)\n".format(funcname) + self._function_string + else: + self._function_string+= "dy[:] .= {}\nend\nend".format(top_var_name) + self._function_string = "function {}(dy,y,p,t)\n".format(funcname) + self._function_string + + - # Replace parameters in the julia strings in the form "inputs[name]" - # with just "name" - for param in model.input_parameters: - mtk_str = mtk_str.replace(f"inputs['{param.name}']", param.name) + return 0 + - # Need to add 'nothing' to the end of the mtk string to avoid errors in MTK v4 - # See https://github.com/SciML/diffeqpy/issues/82 - mtk_str += "nothing\nend\n" - return mtk_str + + + #rework this at some point + def build_julia_code(self,funcname="f"): + for entry in self._intermediate.values(): + if issubclass(type(entry),JuliaBinaryOperation): + self.create_cache(entry) + self.convert_intermediate_to_code(entry) + elif type(entry) is JuliaConstant: + self.create_const(entry) + elif type(entry) is JuliaIndex: + continue + elif type(entry) is JuliaStateVector: + continue + elif type(entry) is JuliaScalar: + continue + elif type(entry) is JuliaBroadcastableFunction: + self.create_cache(entry) + self.convert_intermediate_to_code(entry) + elif type(entry) is JuliaMinimumMaximum: + self.create_cache(entry) + self.convert_intermediate_to_code(entry) + elif type(entry) is JuliaTime: + continue + elif type(entry) is JuliaInput: + continue + elif issubclass(type(entry),JuliaConcatenation): + self.create_cache(entry) + self.convert_intermediate_to_code(entry) + else: + raise NotImplementedError("uh oh") + self.write_function_easy(funcname) + string = self._cache_and_const_string+self._function_string + return string diff --git a/pybamm/expression_tree/operations/evaluate_julia_new.py b/pybamm/expression_tree/operations/evaluate_julia_new.py deleted file mode 100644 index 0f54d87a07..0000000000 --- a/pybamm/expression_tree/operations/evaluate_julia_new.py +++ /dev/null @@ -1,713 +0,0 @@ -# -# Compile a PyBaMM expression tree to Julia Code -# -import pybamm -import numpy as np -import numpy -from scipy import special -import scipy -from collections import OrderedDict -from multimethod import multimethod -from math import floor - -def is_constant_and_can_evaluate(symbol): - """ - Returns True if symbol is constant and evaluation does not raise any errors. - Returns False otherwise. - An example of a constant symbol that cannot be "evaluated" is PrimaryBroadcast(0). - """ - if symbol.is_constant(): - try: - symbol.evaluate() - return True - except NotImplementedError: - return False - else: - return False - -#BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH -class JuliaBinaryOperation(object): - def __init__(self,left_input,right_input,output,shape): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - -#MatMul and Inner Product are not really the same as the bitwisebinary operations. -class JuliaMatrixMultiplication(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - -class JuliaBitwiseBinaryOperation(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape,operator): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - self.operator = operator - -class JuliaAddition(JuliaBinaryOperation): - pass - -class JuliaSubtraction(JuliaBinaryOperation): - pass - -class JuliaMultiplication(JuliaBinaryOperation): - pass - -class JuliaDivision(JuliaBinaryOperation): - pass - -class JuliaPower(JuliaBinaryOperation): - pass - -#MinMax is special because it does both min and max. Could be folded into JuliaBitwiseBinaryOperation once I do that -class JuliaMinMax(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape,name): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - self.name = name - -#FUNCTIONS -##All Functions Return the same number of arguments they take in, except for minimum and maximum. -class JuliaFunction(object): - pass - -class JuliaBroadcastableFunction(JuliaFunction): - def __init__(self,name,input,output,shape): - self.name = name - self.input = input - self.output = output - self.shape = shape - - -#Index is a little weird, so it just sits on its own. -class JuliaIndex(object): - def __init__(self,input,output,index): - self.input = input - self.output = output - self.index = index - if type(index) is slice: - if type(index.step) is None: - self.shape = (slice.start-slice.stop,1) - elif type(index.step) is int: - self.shape = (floor((slice.stop-slice.start)/slice.step),1) - elif type(index) is int: - self.shape = (1,1) - else: - raise NotImplementedError("index must be slice or int") - - - -#Values and Constants -- I will need to change this to inputs, due to t, y, and p. -class JuliaValue(object): - pass - -class JuliaConstant(JuliaValue): - def __init__(self,id,value): - self.id = id - self.value = value - self.shape = value.shape - -class JuliaStateVector(JuliaValue): - def __init__(self,id,loc,shape): - self.id = id - self.loc = loc - self.shape = shape - -class JuliaScalar(JuliaConstant): - def __init__(self,id,value): - self.id = id - self.value = float(value) - self.shape = (1,1) - -class JuliaTime(JuliaScalar): - def __init__(self,id): - self.id = id - self.shape = (1,1) - self.value = None - - -#CONCATENATIONS -class JuliaConcatenation(object): - def __init__(self,output,shape,children): - self.output = output - self.shape = shape - self.children = children - -class JuliaNumpyConcatenation(JuliaConcatenation): - pass - -#NOTE: CURRENTLY THIS BEHAVES EXACTLY LIKE NUMPYCONCATENATION -class JuliaSparseStack(JuliaConcatenation): - pass - - - - -class JuliaConverter(object): - def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit"): - assert not ismtk - - #Characteristics - self._cache_type = cache_type - self._ismtk=ismtk - self._jacobian_type=jacobian_type - self._preallocate=preallocate - self._dae_type = dae_type - - #"Caches" - #Stores Constants to be Declared in the initial cache - #insight: everything is just a line of code - - #INTERMEDIATE: A List of Stuff to do. Keys are ID's and lists are variable names. - self._intermediate = OrderedDict() - - #Cache Dict and Const Dict Host Julia Variable Names to be used to generate the code. - self._cache_dict = OrderedDict() - self._const_dict = OrderedDict() - - self._cache_id = 0 - self._const_id = 0 - - self._cache_and_const_string = "" - self.function_definition = "" - self._function_string = "" - self._return_string = "" - - #know where to go to find a variable. this could be smoother, there will need to be a ton of boilerplate here. - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaConcatenation): - return self._cache_dict[julia_symbol.output] - - @multimethod - def get_result_variable_name(self, julia_symbol:JuliaBinaryOperation): - return self._cache_dict[julia_symbol.output] - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaConstant): - return self._const_dict[julia_symbol.id] - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaScalar): - return julia_symbol.value - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaTime): - return "t" - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaStateVector): - start = julia_symbol.loc[0]+1 - end = julia_symbol.loc[1] - return "(@view y[{}:{}])".format(start,end) - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaBroadcastableFunction): - return self._cache_dict[julia_symbol.output] - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaIndex): - lower_var = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - index = julia_symbol.index - if type(index) is int: - return "{}[{}]".format(lower_var,index+1) - elif type(index) is slice: - if index.step is None: - return "(@view {}[{}:{}])".format(lower_var,index.start+1,index.stop) - elif type(index.step) is int: - return "(@view {}[{}:{}:{}])".format(lower_var,index.start+1,index.step,index.stop) - else: - raise NotImplementedError("Step has to be an integer.") - else: - raise NotImplementedError("Step must be a slice or an int") - - #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. - def break_down_binary(self,symbol): - #Check for constant - assert not is_constant_and_can_evaluate(symbol) - - #We know that this should only have 2 children - assert len(symbol.children)==2 - - #take care of the kids first (this is recursive but multiple-dispatch recursive which is cool) - id_left = self.convert_tree_to_intermediate(symbol.children[0]) - id_right = self.convert_tree_to_intermediate(symbol.children[1]) - my_id = symbol.id - return id_left,id_right,my_id - - def break_down_concatenation(self,symbol): - child_ids = [] - for child in symbol.children: - child_id = self.convert_tree_to_intermediate(child) - child_ids.append(child_id) - first_id = child_ids[0] - num_cols = self._intermediate[first_id].shape[1] - num_rows = 0 - for child_id in child_ids: - child_shape = self._intermediate[child_id].shape - assert num_cols == child_shape[1] - num_rows+=child_shape[0] - shape = (num_rows,num_cols) - return child_ids,shape - - - #Convert-Trees go here - - #Binary trees constructors. All follow the pattern of mat-mul. They need to find their shapes, assuming that the shapes of the nodes one level below them in the expression tree have already been computed. - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.NumpyConcatenation): - my_id = symbol.id - children_julia,shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaNumpyConcatenation(my_id,shape,children_julia) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.SparseStack): - my_id = symbol.id - children_julia,shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaSparseStack(my_id,shape,children_julia) - return my_id - - - @multimethod - def convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): - #Break down the binary tree - id_left,id_right,my_id = self.break_down_binary(symbol) - left_shape = self._intermediate[id_left].shape - right_shape = self._intermediate[id_right].shape - my_shape = (left_shape[0],right_shape[1]) - #Cache the result. - self._intermediate[my_id] = JuliaMatrixMultiplication(id_left,id_right,my_id,my_shape) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) - return my_id - - #Apparently an inner product is a hadamard product in pybamm - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Inner): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Division): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol: pybamm.Addition): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Minimum): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min") - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Maximum): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max") - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Power): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.EqualHeaviside): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,">=") - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.NotEqualHeaviside): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,">") - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Index): - assert len(symbol.children)==1 - id_lower = self.convert_tree_to_intermediate(symbol.children[0]) - my_id = symbol.id - index = symbol.index - self._intermediate[my_id] = JuliaIndex(id_lower,my_id,index) - return my_id - - - - #Convenience function for operations which can have - def find_the_nonscalar(self,id_left,id_right): - left_type = type(self._intermediate[id_left]) - right_type = type(self._intermediate[id_right]) - if issubclass(left_type,JuliaScalar): - return self._intermediate[id_right].shape - elif issubclass(right_type,JuliaScalar): - return self._intermediate[id_left].shape - else: - return self.same_shape(id_left,id_right) - - #to find the shape, there are a number of elements that should just have the shame shape as their children. This function removes boilerplate by implementing those cases - def same_shape(self,id_left,id_right): - left_shape = self._intermediate[id_left].shape - right_shape = self._intermediate[id_right].shape - assert left_shape==right_shape - return left_shape - - - #Functions - #Broadcastable functions have 1 input and 1 output, and the input and output have the same shape. The hard part is that we have to know which is which and pybamm doesn't differentiate between the two. So, we have to do that with an if statement. - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Function): - my_jl_name = symbol.julia_name - assert len(symbol.children)==1 - my_shape = symbol.children[0].shape - input = self.convert_tree_to_intermediate(symbol.children[0]) - my_id = symbol.id - self._intermediate[my_id] = JuliaBroadcastableFunction(my_jl_name,input,my_id,my_shape) - return my_id - - - - #Constants and Values. There are only 2 of these. They must know their own shapes. - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Matrix): - assert is_constant_and_can_evaluate(symbol) - my_id = symbol.id - value = symbol.evaluate() - if value.shape==(1,1): - self._intermediate[my_id] = JuliaScalar(my_id,value) - else: - self._intermediate[my_id] = JuliaConstant(my_id,value) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Vector): - assert is_constant_and_can_evaluate(symbol) - my_id = symbol.id - value = symbol.evaluate() - if value.shape==(1,1): - self._intermediate[my_id] = JuliaScalar(my_id,value) - else: - self._intermediate[my_id] = JuliaConstant(my_id,value) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Scalar): - assert is_constant_and_can_evaluate(symbol) - my_id = symbol.id - value = symbol.evaluate() - self._intermediate[my_id] = JuliaScalar(my_id,value) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Time): - my_id = symbol.id - self._intermediate[my_id] = JuliaTime(my_id) - return my_id - - @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.StateVector): - my_id = symbol.id - first_point = symbol.first_point - last_point = symbol.last_point - points = (first_point,last_point) - shape = symbol.shape - self._intermediate[my_id] = JuliaStateVector(id,points,shape) - return my_id - - #utilities for code conversion - def get_variables_for_binary_tree(self,julia_symbol): - left_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.left_input]) - right_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.right_input]) - result_var_name = self.get_result_variable_name(julia_symbol) - return left_input_var_name,right_input_var_name,result_var_name - - #convert intermediates to code. Again, all binary trees follow the same pattern so we just define a function to break them down, and then use the MD to find out what code to generate. - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaConcatenation): - input_var_names = [] - num_cols = julia_symbol.shape[1] - my_name = self.get_result_variable_name(julia_symbol) - - #assume we don't have tensors. Already asserted that concatenations have to have the same width. - if num_cols==1: - right_parenthesis = "]" - vec=True - else: - right_parenthesis = ",:]" - vec=False - #do the 0th one outside of the loop to initialize - child = julia_symbol.children[0] - child_var = self._intermediate[child] - child_var_name = self.get_result_variable_name(self._intermediate[child]) - start_row = 1 - if child_var.shape[0] == 0: - end_row = 1 - code = "" - elif child_var.shape[0] == 1: - end_row = 1 - if vec: - code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) - else: - code = "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) - else: - start_row = 1 - end_row = child_var.shape[0] - code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) - - for child in julia_symbol.children[1:]: - child_var = self._intermediate[child] - child_var_name = self.get_result_variable_name(self._intermediate[child]) - if child_var.shape[0] == 0: - continue - elif child_var.shape[0] == 1: - start_row = end_row+1 - end_row = start_row+1 - if vec: - code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) - else: - code += "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) - else: - start_row = end_row+1 - end_row = start_row+child_var.shape[0]-1 - code += "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) - - self._function_string+=code - return 0 - - - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): - result_var_name = self.get_result_variable_name(julia_symbol) - input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaMinMax): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) - else: - code = "{} = {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaPower): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {}.^{}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {}.^{})\n".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaBitwiseBinaryOperation): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {}.{}{}\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) - else: - code = "{} = {}.{}{})\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) - self._function_string+=code - return 0 - - #Cache and Const Creation - @multimethod - def create_cache(self,symbol): - my_id = symbol.output - - cache_shape = self._intermediate[my_id].shape - - cache_id = self._cache_id+1 - self._cache_id = cache_id - - cache_name = "cache_{}".format(cache_id) - self._cache_dict[symbol.output] = "cs."+cache_name - - if self._cache_type=="standard": - if cache_shape[1] == 1: - cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) - return 0 - else: - raise NotImplementedError("uh oh") - - - - def create_const(self,symbol): - my_id = symbol.id - const_id = self._const_id+1 - self._const_id = const_id - const_name = "const_{}".format(const_id) - self._const_dict[my_id] = "cs."+const_name - mat_value = symbol.value - print(type(mat_value)) - val_line = self.write_const(mat_value) - const_line = const_name+" = {},\n".format(val_line) - self._cache_and_const_string+=const_line - return 0 - - @multimethod - def write_const(self,mat_value:numpy.ndarray): - return mat_value - - @multimethod - def write_const(self,value:scipy.sparse._csr.csr_matrix): - row, col, data = scipy.sparse.find(value) - m, n = value.shape - np.set_printoptions( - threshold=max(np.get_printoptions()["threshold"], len(row) + 10) - ) - - val_string = "sparse({}, {}, {}, {}, {})".format( - np.array2string(row + 1, separator=","), - np.array2string(col + 1, separator=","), - np.array2string(data, separator=","), - m, - n, - ) - return val_string - - #Just get something working here, so can start actual testing - def write_function_easy(self): - #start with the closure - self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string - self._cache_and_const_string += ")\n" - - - top = self._intermediate[next(reversed(self._intermediate))] - top_var_name = self.get_result_variable_name(top) - my_shape = top.shape - if my_shape[1] != 1: - self._function_string += "J[:,:] .= {}\nend\nend".format(top_var_name) - self._function_string = "function model(J,y,p,t)\n" + self._function_string - else: - self._function_string+= "dy[:] .= {}\nend\nend".format(top_var_name) - self._function_string = "function model(dy,y,p,t)\n" + self._function_string - - - - return 0 - - - - - - #rework this at some point - def build_julia_code(self): - for entry in self._intermediate.values(): - print(entry) - if issubclass(type(entry),JuliaBinaryOperation): - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - elif type(entry) is JuliaConstant: - self.create_const(entry) - elif type(entry) is JuliaIndex: - continue - elif type(entry) is JuliaStateVector: - continue - elif type(entry) is JuliaScalar: - continue - elif type(entry) is JuliaBroadcastableFunction: - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - elif type(entry) is JuliaTime: - continue - elif type(entry) in [JuliaNumpyConcatenation,JuliaSparseStack]: - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - else: - raise NotImplementedError("uh oh") - self.write_function_easy() - string = self._cache_and_const_string+self._function_string - return string - -def fuck_me(): - print("fuck") - \ No newline at end of file diff --git a/pybamm/expression_tree/operations/evaluate_julia_old.py b/pybamm/expression_tree/operations/evaluate_julia_old.py new file mode 100644 index 0000000000..0eea013f4b --- /dev/null +++ b/pybamm/expression_tree/operations/evaluate_julia_old.py @@ -0,0 +1,1142 @@ +# +# Write a symbol to Julia +# +import pybamm + +import numpy as np +import scipy.sparse +from collections import OrderedDict + +import numbers + + +def id_to_julia_variable(symbol_id, prefix): + """ + This function defines the format for the julia variable names used in find_symbols + and to_julia. Variable names are based on a nodes' id to make them unique + """ + var_format = prefix + "_{:05d}" + # Need to replace "-" character to make them valid julia variable names + return var_format.format(symbol_id).replace("-", "m") + + +def is_constant_and_can_evaluate(symbol): + """ + Returns True if symbol is constant and evaluation does not raise any errors. + Returns False otherwise. + An example of a constant symbol that cannot be "evaluated" is PrimaryBroadcast(0). + """ + if symbol.is_constant(): + try: + symbol.evaluate() + return True + except NotImplementedError: + return False + else: + return False + + +def find_symbols( + symbol, + constant_symbols, + variable_symbols, + variable_symbol_sizes, + round_constants=True, +): + """ + This function converts an expression tree to a dictionary of node id's and strings + specifying valid julia code to calculate that nodes value, given y and t. + + The function distinguishes between nodes that represent constant nodes in the tree + (e.g. a pybamm.Matrix), and those that are variable (e.g. subtrees that contain + pybamm.StateVector). The former are put in `constant_symbols`, the latter in + `variable_symbols` + + Note that it is important that the arguments `constant_symbols` and + `variable_symbols` be and *ordered* dict, since the final ordering of the code lines + are important for the calculations. A dict is specified rather than a list so that + identical subtrees (which give identical id's) are not recalculated in the code + + Parameters + ---------- + symbol : :class:`pybamm.Symbol` + The symbol or expression tree to convert + + constant_symbol : collections.OrderedDict + The output dictionary of constant symbol ids to lines of code + + variable_symbol : collections.OrderedDict + The output dictionary of variable (with y or t) symbol ids to lines of code + + variable_symbol_sizes : collections.OrderedDict + The output dictionary of variable (with y or t) symbol ids to size of that + variable, for caching + + """ + # ignore broadcasts for now + if isinstance(symbol, pybamm.Broadcast): + symbol = symbol.child + if is_constant_and_can_evaluate(symbol): + value = symbol.evaluate() + if round_constants: + value = np.round(value, decimals=11) + if not isinstance(value, numbers.Number): + if scipy.sparse.issparse(value): + # Create Julia SparseArray + row, col, data = scipy.sparse.find(value) + if round_constants: + data = np.round(data, decimals=11) + m, n = value.shape + # Set print options large enough to avoid ellipsis + # at least as big is len(row) = len(col) = len(data) + np.set_printoptions( + threshold=max(np.get_printoptions()["threshold"], len(row) + 10) + ) + # increase precision for printing + np.set_printoptions(precision=20) + # add 1 to correct for 1-indexing in Julia + # use array2string so that commas are included + constant_symbols[symbol.id] = "sparse({}, {}, {}, {}, {})".format( + np.array2string(row + 1, separator=","), + np.array2string(col + 1, separator=","), + np.array2string(data, separator=","), + m, + n, + ) + variable_symbol_sizes[symbol.id] = symbol.shape + elif value.shape == (1, 1): + # Extract value if array has only one entry + constant_symbols[symbol.id] = value[0, 0] + variable_symbol_sizes[symbol.id] = 1 + elif value.shape[1] == 1: + # Set print options large enough to avoid ellipsis + # at least as big as len(row) = len(col) = len(data) + np.set_printoptions( + threshold=max( + np.get_printoptions()["threshold"], value.shape[0] + 10 + ) + ) + # Flatten a 1D array + constant_symbols[symbol.id] = np.array2string( + value.flatten(), separator="," + ) + variable_symbol_sizes[symbol.id] = symbol.shape[0] + else: + constant_symbols[symbol.id] = value + # No need to save the size as it will not need to be used + return + + # process children recursively + for child in symbol.children: + find_symbols( + child, + constant_symbols, + variable_symbols, + variable_symbol_sizes, + round_constants=round_constants, + ) + + # calculate the variable names that will hold the result of calculating the + # children variables + children_vars = [] + for child in symbol.children: + if isinstance(child, pybamm.Broadcast): + child = child.child + if is_constant_and_can_evaluate(child): + child_eval = child.evaluate() + if isinstance(child_eval, numbers.Number): + children_vars.append(str(child_eval)) + else: + children_vars.append(id_to_julia_variable(child.id, "const")) + else: + children_vars.append(id_to_julia_variable(child.id, "cache")) + + if isinstance(symbol, pybamm.BinaryOperator): + # TODO: we can pass through a dummy y and t to get the type and then hardcode + # the right line, avoiding these checks + if isinstance(symbol, pybamm.MatrixMultiplication): + symbol_str = "{0} @ {1}".format(children_vars[0], children_vars[1]) + elif isinstance(symbol, pybamm.Inner): + symbol_str = "{0} * {1}".format(children_vars[0], children_vars[1]) + elif isinstance(symbol, pybamm.Minimum): + symbol_str = "min({},{})".format(children_vars[0], children_vars[1]) + elif isinstance(symbol, pybamm.Maximum): + symbol_str = "max({},{})".format(children_vars[0], children_vars[1]) + elif isinstance(symbol, pybamm.Power): + # julia uses ^ instead of ** for power + # include dot for elementwise operations + symbol_str = children_vars[0] + " .^ " + children_vars[1] + else: + # all other operations use the same symbol + symbol_str = children_vars[0] + " " + symbol.name + " " + children_vars[1] + + elif isinstance(symbol, pybamm.UnaryOperator): + # Index has a different syntax than other univariate operations + if isinstance(symbol, pybamm.Index): + # Because of how julia indexing works, add 1 to the start, but not to the + # stop + symbol_str = "{}[{}:{}]".format( + children_vars[0], symbol.slice.start + 1, symbol.slice.stop + ) + elif isinstance(symbol, pybamm.Gradient): + symbol_str = "grad_{}({})".format(tuple(symbol.domain), children_vars[0]) + elif isinstance(symbol, pybamm.Divergence): + symbol_str = "div_{}({})".format(tuple(symbol.domain), children_vars[0]) + elif isinstance(symbol, pybamm.Broadcast): + # ignore broadcasts for now + symbol_str = children_vars[0] + elif isinstance(symbol, pybamm.BoundaryValue): + symbol_str = "boundary_value_{}({})".format(symbol.side, children_vars[0]) + else: + symbol_str = symbol.name + children_vars[0] + + elif isinstance(symbol, pybamm.Function): + # write functions directly + symbol_str = "{}({})".format(symbol.julia_name, ", ".join(children_vars)) + + elif isinstance(symbol, (pybamm.Variable, pybamm.ConcatenationVariable)): + # No need to do anything if a Variable is found + return + + elif isinstance(symbol, pybamm.Concatenation): + if isinstance(symbol, (pybamm.NumpyConcatenation, pybamm.SparseStack)): + # return a list of the children variables, which will be converted to a + # line by line assignment + # return this as a string so that other functionality still works + # also save sizes + symbol_str = "[" + for child in children_vars: + child_id = child[6:].replace("m", "-") + size = variable_symbol_sizes[int(child_id)] + symbol_str += "{}::{}, ".format(size, child) + symbol_str = symbol_str[:-2] + "]" + + # DomainConcatenation specifies a particular ordering for the concatenation, + # which we must follow + elif isinstance(symbol, pybamm.DomainConcatenation): + if symbol.secondary_dimensions_npts == 1: + all_child_vectors = children_vars + all_child_sizes = [ + variable_symbol_sizes[int(child[6:].replace("m", "-"))] + for child in children_vars + ] + else: + slice_starts = [] + all_child_vectors = [] + all_child_sizes = [] + for i in range(symbol.secondary_dimensions_npts): + child_vectors = [] + child_sizes = [] + for child_var, slices in zip( + children_vars, symbol._children_slices + ): + for child_dom, child_slice in slices.items(): + slice_starts.append(symbol._slices[child_dom][i].start) + # add 1 to slice start to account for julia indexing + child_vectors.append( + "@view {}[{}:{}]".format( + child_var, + child_slice[i].start + 1, + child_slice[i].stop, + ) + ) + child_sizes.append( + child_slice[i].stop - child_slice[i].start + ) + all_child_vectors.extend( + [v for _, v in sorted(zip(slice_starts, child_vectors))] + ) + all_child_sizes.extend( + [v for _, v in sorted(zip(slice_starts, child_sizes))] + ) + # return a list of the children variables, which will be converted to a + # line by line assignment + # return this as a string so that other functionality still works + # also save sizes + symbol_str = "[" + for child, size in zip(all_child_vectors, all_child_sizes): + symbol_str += "{}::{}, ".format(size, child) + symbol_str = symbol_str[:-2] + "]" + + else: + # A regular Concatenation for the MTK model + # We will define the concatenation function separately + symbol_str = "concatenation(" + ", ".join(children_vars) + ")" + + # Note: we assume that y is being passed as a column vector + elif isinstance(symbol, pybamm.StateVectorBase): + if isinstance(symbol, pybamm.StateVector): + name = "@view y" + elif isinstance(symbol, pybamm.StateVectorDot): + name = "@view dy" + indices = np.argwhere(symbol.evaluation_array).reshape(-1).astype(np.int32) + # add 1 since julia uses 1-indexing + indices += 1 + if len(indices) == 1: + symbol_str = "{}[{}]".format(name, indices[0]) + else: + # julia does include the final value + symbol_str = "{}[{}:{}]".format(name, indices[0], indices[-1]) + + elif isinstance(symbol, pybamm.Time): + symbol_str = "t" + + elif isinstance(symbol, pybamm.InputParameter): + symbol_str = "inputs['{}']".format(symbol.name) + + elif isinstance(symbol, pybamm.SpatialVariable): + symbol_str = symbol.name + + elif isinstance(symbol, pybamm.FunctionParameter): + symbol_str = "{}({})".format(symbol.name, ", ".join(children_vars)) + + else: + raise NotImplementedError( + "Conversion to Julia not implemented for a symbol of type '{}'".format( + type(symbol) + ) + ) + + variable_symbols[symbol.id] = symbol_str + + # Save the size of the symbol + try: + if symbol.shape == (): + variable_symbol_sizes[symbol.id] = 1 + else: + variable_symbol_sizes[symbol.id] = symbol.shape[0] + except NotImplementedError: + pass + + +def to_julia(symbol, round_constants=True): + """ + This function converts an expression tree into a dict of constant input values, and + valid julia code that acts like the tree's :func:`pybamm.Symbol.evaluate` function + + Parameters + ---------- + symbol : :class:`pybamm.Symbol` + The symbol to convert to julia code + + Returns + ------- + constant_values : collections.OrderedDict + dict mapping node id to a constant value. Represents all the constant nodes in + the expression tree + str + valid julia code that will evaluate all the variable nodes in the tree. + + """ + + constant_values = OrderedDict() + variable_symbols = OrderedDict() + variable_symbol_sizes = OrderedDict() + find_symbols( + symbol, + constant_values, + variable_symbols, + variable_symbol_sizes, + round_constants=round_constants, + ) + + return constant_values, variable_symbols, variable_symbol_sizes + + +def get_julia_function( + symbol, + funcname="f", + input_parameter_order=None, + len_rhs=None, + preallocate=True, + round_constants=True, + cache_type="standard" +): + """ + Converts a pybamm expression tree into pure julia code that will calculate the + result of calling `evaluate(t, y)` on the given expression tree. + + Parameters + ---------- + symbol : :class:`pybamm.Symbol` + The symbol to convert to julia code + funcname : str, optional + The name to give to the function (default 'f') + input_parameter_order : list, optional + List of input parameter names. Defines the order in which the input parameters + are extracted from 'p' in the julia function that is created + len_rhs : int, optional + The number of ODEs in the discretized differential equations. This also + determines whether the model has any algebraic equations: if None (default), + the model is assume to have no algebraic parts and ``julia_str`` is compatible + with an ODE solver. If not None, ``julia_str`` is compatible with a DAE solver + preallocate : bool, optional + Whether to write the function in a way that preallocates memory for the output. + Default is True, which is faster. Must be False for the function to be + modelingtoolkitized. + cache_type : str, optional + The type of cache to use for the function. Must be one of 'standard', 'dual', 'symbolic', + or 'gpu'. If 'standard', the function will be cached in the standard way, + If 'dual', the function will use the dualcache provided by preallocationtools.jl, + and if 'symbolic', the function will use the symcache provided by PyBaMM.jl. Default + is standard, and as of so far, I haven't been able to beat it with performance yet. + + Returns + ------- + julia_str : str + String of julia code, to be evaluated by ``julia.Main.eval`` + + """ + if len_rhs is None: + typ = "ode" + else: + typ = "dae" + # Take away dy from the differential states + # we will return a function of the form + # out[] = .. - dy[] for the differential states + # out[] = .. for the algebraic states + symbol_minus_dy = [] + end = 0 + for child in symbol.orphans: + start = end + end += child.size + if end <= len_rhs: + symbol_minus_dy.append(child - pybamm.StateVectorDot(slice(start, end))) + else: + symbol_minus_dy.append(child) + symbol = pybamm.numpy_concatenation(*symbol_minus_dy) + constants, var_symbols, var_symbol_sizes = to_julia( + symbol, round_constants=round_constants + ) + + # extract constants in generated function + const_and_cache_str = "cs = (\n" + shorter_const_names = {} + for i_const, (symbol_id, const_value) in enumerate(constants.items()): + const_name = id_to_julia_variable(symbol_id, "const") + const_name_short = "const_{}".format(i_const) + if cache_type=="gpu": + const_and_cache_str += " {} = cu({}),\n".format(const_name_short, const_value) + else: + const_and_cache_str += " {} = {},\n".format(const_name_short, const_value) + shorter_const_names[const_name] = const_name_short + + # Pop (get and remove) items from the dictionary of symbols one by one + # If they are simple operations (@view, +, -, *, /), replace all future + # occurences instead of assigning them. This "inlining" speeds up the computation + inlineable_symbols = ["@view", "+", "-", "*", "/"] + var_str = "" + input_parameters = {} + while var_symbols: + var_symbol_id, symbol_line = var_symbols.popitem(last=False) + julia_var = id_to_julia_variable(var_symbol_id, "cache") + # Look for lists in the variable symbols. These correspond to concatenations, so + # assign the children to the right parts of the vector + #symbol_line_split = symbol_line.split(", ") + #var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) + if symbol_line[0] == "[" and symbol_line[-1] == "]": + # convert to actual list + symbol_line = symbol_line[1:-1].split(", ") + new_list = [] + #make sure we haven't split a tuple + kill=False + for i,thing in enumerate(symbol_line): + if kill: + continue + elif thing[0] == '(': + next_thing = symbol_line[i+1] + thing = thing+next_thing + new_list.append(thing) + symbol_line = new_list + start = 0 + if preallocate is True or var_symbol_id == symbol.id: + for child_size_and_name in symbol_line: + child_size, child_name = child_size_and_name.split("::") + end = start + int(child_size) + # add 1 to start to account for julia 1-indexing + var_str += "@. {}[{}:{}] = {}\n".format( + julia_var, start + 1, end, child_name + ) + start = end + else: + concat_str = "{} = vcat(".format(julia_var) + for i, child_size_and_name in enumerate(symbol_line): + child_size, child_name = child_size_and_name.split("::") + var_str += "x{} = @. {}\n".format(i + 1, child_name) + concat_str += "x{}, ".format(i + 1) + var_str += concat_str[:-2] + ")\n" + # use mul! for matrix multiplications (requires LinearAlgebra library) + elif " @ " in symbol_line: + if preallocate is False: + symbol_line = symbol_line.replace(" @ ", " * ") + var_str += "{} = {}\n".format(julia_var, symbol_line) + else: + symbol_line = symbol_line.replace(" @ ", ", ") + var_str += "mul!({}, {})\n".format(julia_var, symbol_line) + # find input parameters + elif symbol_line.startswith("inputs"): + input_parameters[julia_var] = symbol_line[8:-2] + elif "minimum" in symbol_line or "maximum" in symbol_line: + var_str += "{} .= {}\n".format(julia_var, symbol_line) + else: + # don't replace the matrix multiplication cases (which will be + # turned into a mul!), since it is faster to assign to a cache array + # first in that case + # e.g. mul!(cs.cache_1, cs.cache_2, cs.cache_3) + # unless it is a @view in which case we don't + # need to cache + # e.g. mul!(cs.cache_1, cs.cache_2, @view y[1:10]) + # also don't replace the minimum() or maximum() cases as we can't + # broadcast them + any_matmul_min_max = any( + julia_var in next_symbol_line + and ( + any( + x in next_symbol_line + for x in [" @ ", "mul!", "minimum", "maximum"] + ) + and not symbol_line.startswith("@view") + ) + for next_symbol_line in var_symbols.values() + ) + # inline operation if it can be inlined + if ( + any(x in symbol_line for x in inlineable_symbols) or symbol_line == "t" + ) and not any_matmul_min_max: + found_replacement = False + # replace all other occurrences of the variable + # in the dictionary with the symbol line + for next_var_id, next_symbol_line in var_symbols.items(): + if julia_var in next_symbol_line: + if symbol_line == "t": + # no brackets needed + var_symbols[next_var_id] = next_symbol_line.replace( + julia_var, symbol_line + ) + else: + # add brackets so that the order of operations is maintained + var_symbols[next_var_id] = next_symbol_line.replace( + julia_var, "({})".format(symbol_line) + ) + found_replacement = True + if not found_replacement: + var_str += "@. {} = {}\n".format(julia_var, symbol_line) + + # otherwise assign + else: + var_str += "@. {} = {}\n".format(julia_var, symbol_line) + # Replace all input parameter names + for input_parameter_id, input_parameter_name in input_parameters.items(): + var_str = var_str.replace(input_parameter_id, input_parameter_name) + + # indent code + var_str = " " + var_str + var_str = var_str.replace("\n", "\n ") + + + cache_initialization_str = "" + + # add the cache variables to the cache NamedTuple + i_cache = 0 + for var_symbol_id, var_symbol_size in var_symbol_sizes.items(): + # Skip caching the result variable since this is provided as dy + # Also skip caching the result variable if it doesn't appear in the var_str, + # since it has been inlined and does not need to be assigned to + julia_var = id_to_julia_variable(var_symbol_id, "cache") + if var_symbol_id != symbol.id and julia_var in var_str: + julia_var_short = "cache_{}".format(i_cache) + var_str = var_str.replace(julia_var, julia_var_short) + i_cache += 1 + if preallocate is True: + if cache_type == "symbolic": + const_and_cache_str += " {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format( + julia_var_short, var_symbol_size,var_symbol_size + ) + cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + elif cache_type == "standard": + const_and_cache_str += " {} = zeros({}),\n".format( + julia_var_short, var_symbol_size + ) + elif cache_type == "dual": + const_and_cache_str += " {} = dualcache(zeros({}),12),\n".format( + julia_var_short, var_symbol_size + ) + cache_initialization_str += " {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) + elif cache_type == "gpu": + const_and_cache_str+=" {} = CUDA.zeros({}),\n".format(julia_var_short,var_symbol_size) + + else: + # Cache variables have not been preallocated + var_str = var_str.replace( + "@. {} = ".format(julia_var_short), + "{} = @. ".format(julia_var_short), + ) + + # Shorten the name of the constants from id to const_0, const_1, etc. + for long, short in shorter_const_names.items(): + var_str = var_str.replace(long, "cs." + short) + + # close the constants and cache string + const_and_cache_str += ")\n" + + # remove the constant and cache sring if it is empty + const_and_cache_str = const_and_cache_str.replace("cs = (\n)\n", "") + + # calculate the final variable that will output the result + if symbol.is_constant(): + result_var = id_to_julia_variable(symbol.id, "const") + if result_var in shorter_const_names: + result_var = shorter_const_names[result_var] + result_value = symbol.evaluate() + if isinstance(result_value, numbers.Number): + var_str = var_str + "\n dy .= " + str(result_value) + "\n" + else: + var_str = var_str + "\n dy .= cs." + result_var + "\n" + else: + result_var = id_to_julia_variable(symbol.id, "cache") + if typ == "ode": + out = "dy" + elif typ == "dae": + out = "out" + # replace "cache_123 = ..." with "dy .= ..." (ensure we allocate to the + # variable that was passed in) + var_str = var_str.replace(f" {result_var} =", f" {out} .=") + # catch other cases for dy + var_str = var_str.replace(result_var, out) + + # add "cs." to cache names + if preallocate is True: + if cache_type in ["standard","gpu"]: + var_str = var_str.replace("cache", "cs.cache") + + # line that extracts the input parameters in the right order + if input_parameter_order is None: + input_parameter_extraction = "" + elif len(input_parameter_order) == 1: + # extract the single parameter + input_parameter_extraction = " " + input_parameter_order[0] + " = p[1]\n" + else: + # extract all parameters + input_parameter_extraction = " " + ", ".join(input_parameter_order) + " = p\n" + + if preallocate is False or const_and_cache_str == "": + func_def = f"{funcname}!" + else: + func_def = f"{funcname}_with_consts!" + + # add function def + if typ == "ode": + function_def = f"\nfunction {func_def}(dy, y, p, t)\n" + elif typ == "dae": + function_def = f"\nfunction {func_def}(out, dy, y, p, t)\n" + julia_str = ( + "begin\n" + + const_and_cache_str + + function_def + + cache_initialization_str + + input_parameter_extraction + + var_str + ) + + # close the function, with a 'nothing' to avoid allocations + julia_str += "nothing\nend\n\n" + julia_str = julia_str.replace("\n \n", "\n") + + if not (preallocate is False or const_and_cache_str == ""): + # Use a let block for the cached variables + # open the let block + julia_str = julia_str.replace("cs = (", f"{funcname}! = let cs = (") + # close the let block + julia_str += "end\n" + + # close the "begin" + julia_str += "end" + + return julia_str + + +def convert_var_and_eqn_to_str(var, eqn, all_constants_str, all_variables_str, typ): + """ + Converts a variable and its equation to a julia string + + Parameters + ---------- + var : :class:`pybamm.Symbol` + The variable (key in the dictionary of rhs/algebraic/initial conditions) + eqn : :class:`pybamm.Symbol` + The equation (value in the dictionary of rhs/algebraic/initial conditions) + all_constants_str : str + String containing all the constants defined so far + all_variables_str : str + String containing all the variables defined so far + typ : str + The type of the variable/equation pair being converted ("equation", "initial + condition", or "boundary condition") + + Returns + ------- + all_constants_str : str + Updated string of all constants + all_variables_str : str + Updated string of all variables + eqn_str : str + The string describing the final equation result, perhaps as a function of some + variables and/or constants in all_constants_str and all_variables_str + + """ + if isinstance(eqn, pybamm.Broadcast): + # ignore broadcasts for now + eqn = eqn.child + + var_symbols = to_julia(eqn)[1] + + # var_str = "" + # for symbol_id, symbol_line in var_symbols.items(): + # var_str += f"{id_to_julia_variable(symbol_id)} = {symbol_line}\n" + # Pop (get and remove) items from the dictionary of symbols one by one + # If they are simple operations (+, -, *, /), replace all future + # occurences instead of assigning them. + inlineable_symbols = [" + ", " - ", " * ", " / "] + var_str = "" + while var_symbols: + var_symbol_id, symbol_line = var_symbols.popitem(last=False) + julia_var = id_to_julia_variable(var_symbol_id, "cache") + # inline operation if it can be inlined + if "concatenation" not in symbol_line: + found_replacement = False + # replace all other occurrences of the variable + # in the dictionary with the symbol line + for next_var_id, next_symbol_line in var_symbols.items(): + if ( + symbol_line == "t" + or " " not in symbol_line + or symbol_line.startswith("grad") + or not any(x in next_symbol_line for x in inlineable_symbols) + ): + # cases that don't need brackets + var_symbols[next_var_id] = next_symbol_line.replace( + julia_var, symbol_line + ) + elif next_symbol_line.startswith("concatenation"): + var_symbols[next_var_id] = next_symbol_line.replace( + julia_var, f"\n {symbol_line}\n" + ) + else: + # add brackets so that the order of operations is maintained + var_symbols[next_var_id] = next_symbol_line.replace( + julia_var, "({})".format(symbol_line) + ) + found_replacement = True + if not found_replacement: + var_str += "{} = {}\n".format(julia_var, symbol_line) + + # otherwise assign + else: + var_str += "{} = {}\n".format(julia_var, symbol_line) + + # If we have created a concatenation we need to define it + # Hardcoded to the negative electrode, separator, positive electrode case for now + if "concatenation" in var_str and "function concatenation" not in all_variables_str: + concatenation_def = ( + "\nfunction concatenation(n, s, p)\n" + + " # A concatenation in the electrolyte domain\n" + + " IfElse.ifelse(\n" + + " x < neg_width, n, IfElse.ifelse(\n" + + " x < neg_plus_sep_width, s, p\n" + + " )\n" + + " )\n" + + "end\n" + ) + else: + concatenation_def = "" + + # Define the FunctionParameter objects that have not yet been defined + function_defs = "" + for x in eqn.pre_order(): + if ( + isinstance(x, pybamm.FunctionParameter) + and f"function {x.name}" not in all_variables_str + and typ == "equation" + ): + function_def = ( + f"\nfunction {x.name}(" + + ", ".join(x.arg_names) + + ")\n" + + " {}\n".format(str(x.callable).replace("**", "^")) + + "end\n" + ) + function_defs += function_def + + if concatenation_def + function_defs != "": + function_defs += "\n" + + var_str = concatenation_def + function_defs + var_str + + # add a comment labeling the equation, and the equation itself + if var_str == "": + all_variables_str += "" + else: + all_variables_str += f"# '{var.name}' {typ}\n" + var_str + "\n" + + # calculate the final variable that will output the result + if eqn.is_constant(): + result_var = id_to_julia_variable(eqn.id, "const") + else: + result_var = id_to_julia_variable(eqn.id, "cache") + if is_constant_and_can_evaluate(eqn): + result_value = eqn.evaluate() + else: + result_value = None + + # define the variable that goes into the equation + if eqn.is_constant() and isinstance(result_value, numbers.Number): + eqn_str = str(result_value) + else: + eqn_str = result_var + + return all_constants_str, all_variables_str, eqn_str + + +def get_julia_mtk_model(model, geometry=None, tspan=None): + """ + Converts a pybamm model into a Julia ModelingToolkit model + + Parameters + ---------- + model : :class:`pybamm.BaseModel` + The model to be converted + geometry : dict, optional + Dictionary defining the geometry. Must be provided if the model is a PDE model + tspan : array-like, optional + Time for which to solve the model. Must be provided if the model is a PDE model + + Returns + ------- + mtk_str : str + String of julia code representing a model in MTK, + to be evaluated by ``julia.Main.eval`` + """ + # Extract variables + variables = {**model.rhs, **model.algebraic}.keys() + variable_to_print_name = {} + for i, var in enumerate(variables): + if var.print_name is not None: + print_name = var._raw_print_name + else: + print_name = f"u{i+1}" + variable_to_print_name[var] = print_name + if isinstance(var, pybamm.ConcatenationVariable): + for child in var.children: + variable_to_print_name[child] = print_name + + # Extract domain and auxiliary domains + all_domains = set( + [tuple(dom) for var in variables for dom in var.domains.values() if dom != []] + ) + is_pde = bool(all_domains) + + # Check geometry and tspan have been provided if a PDE + if is_pde: + if geometry is None: + raise ValueError("must provide geometry if the model is a PDE model") + if tspan is None: + raise ValueError("must provide tspan if the model is a PDE model") + + # Read domain names + domain_name_to_symbol = {} + long_domain_symbol_to_short = {} + for dom in all_domains: + # Read domain name from geometry + domain_symbol = list(geometry[dom[0]].keys())[0] + if len(dom) > 1: + domain_symbol = domain_symbol[0] + # For multi-domain variables keep only the first letter of the domain + domain_name_to_symbol[tuple(dom)] = domain_symbol + # Record which domain symbols we shortened + for d in dom: + long = list(geometry[d].keys())[0] + long_domain_symbol_to_short[long] = domain_symbol + else: + # Otherwise keep the whole domain + domain_name_to_symbol[tuple(dom)] = domain_symbol + + # Read domain limits + domain_name_to_limits = {(): None} + for dom in all_domains: + limits = list(geometry[dom[0]].values())[0].values() + if len(limits) > 1: + lower_limit, _ = list(geometry[dom[0]].values())[0].values() + _, upper_limit = list(geometry[dom[-1]].values())[0].values() + domain_name_to_limits[tuple(dom)] = ( + lower_limit.evaluate(), + upper_limit.evaluate(), + ) + else: + # Don't record limits for variables that have "limits" of length 1 i.e. + # a zero-dimensional domain + domain_name_to_limits[tuple(dom)] = None + + # Define independent variables for each variable + var_to_ind_vars = {} + var_to_ind_vars_left_boundary = {} + var_to_ind_vars_right_boundary = {} + for var in variables: + if var.domain in [[], ["current collector"]]: + var_to_ind_vars[var] = "(t)" + else: + # all independent variables e.g. (t, x) or (t, rn, xn) + domain_symbols = ", ".join( + domain_name_to_symbol[tuple(dom)] + for dom in var.domains.values() + if domain_name_to_limits[tuple(dom)] is not None + ) + var_to_ind_vars[var] = f"(t, {domain_symbols})" + if isinstance(var, pybamm.ConcatenationVariable): + for child in var.children: + var_to_ind_vars[child] = f"(t, {domain_symbols})" + aux_domain_symbols = ", ".join( + domain_name_to_symbol[tuple(dom)] + for level, dom in var.domains.items() + if level != "primary" and domain_name_to_limits[tuple(dom)] is not None + ) + if aux_domain_symbols != "": + aux_domain_symbols = ", " + aux_domain_symbols + + limits = domain_name_to_limits[tuple(var.domain)] + # left bc e.g. (t, 0) or (t, 0, xn) + var_to_ind_vars_left_boundary[var] = f"(t, {limits[0]}{aux_domain_symbols})" + # right bc e.g. (t, 1) or (t, 1, xn) + var_to_ind_vars_right_boundary[ + var + ] = f"(t, {limits[1]}{aux_domain_symbols})" + + mtk_str = "begin\n" + # Define parameters (including independent variables) + # Makes a line of the form '@parameters t x1 x2 x3 a b c d' + ind_vars = ["t"] + [ + sym + for dom, sym in domain_name_to_symbol.items() + if domain_name_to_limits[dom] is not None + ] + for domain_name, domain_symbol in domain_name_to_symbol.items(): + if domain_name_to_limits[domain_name] is not None: + mtk_str += f"# {domain_name} -> {domain_symbol}\n" + mtk_str += "@parameters " + " ".join(ind_vars) + if len(model.input_parameters) > 0: + mtk_str += "\n# Input parameters\n@parameters" + for param in model.input_parameters: + mtk_str += f" {param.name}" + mtk_str += "\n" + + # Add a comment with the variable names + for var in variables: + mtk_str += f"# '{var.name}' -> {variable_to_print_name[var]}\n" + # Makes a line of the form '@variables u1(t) u2(t)' + dep_vars = [] + mtk_str += "@variables" + for var in variables: + mtk_str += f" {variable_to_print_name[var]}(..)" + dep_var = variable_to_print_name[var] + var_to_ind_vars[var] + dep_vars.append(dep_var) + mtk_str += "\n" + + # Define derivatives + for domain_symbol in ind_vars: + mtk_str += f"D{domain_symbol} = Differential({domain_symbol})\n" + mtk_str += "\n" + + # Define equations + all_eqns_str = "" + all_constants_str = "" + all_julia_str = "" + for var, eqn in {**model.rhs, **model.algebraic}.items(): + all_constants_str, all_julia_str, eqn_str = convert_var_and_eqn_to_str( + var, eqn, all_constants_str, all_julia_str, "equation" + ) + + if var in model.rhs: + all_eqns_str += ( + f" Dt({variable_to_print_name[var]}{var_to_ind_vars[var]}) " + + f"~ {eqn_str},\n" + ) + elif var in model.algebraic: + all_eqns_str += f" 0 ~ {eqn_str},\n" + + # Replace any long domain symbols with the short version + # e.g. "xn" gets replaced with "x" + for long, short in long_domain_symbol_to_short.items(): + # we need to add a space to avoid accidentally replacing 'exp' with 'ex' + all_julia_str = all_julia_str.replace(" " + long, " " + short) + + # Replace variables in the julia strings that correspond to pybamm variables with + # their julia equivalent + for var, julia_id in variable_to_print_name.items(): + # e.g. boundary_value_right(cache_123456789) gets replaced with u1(t, 1) + cache_var_id = id_to_julia_variable(var.id, "cache") + if f"boundary_value_right({cache_var_id})" in all_julia_str: + all_julia_str = all_julia_str.replace( + f"boundary_value_right({cache_var_id})", + julia_id + var_to_ind_vars_right_boundary[var], + ) + # e.g. cache_123456789 gets replaced with u1(t, x) + all_julia_str = all_julia_str.replace( + cache_var_id, julia_id + var_to_ind_vars[var] + ) + + # Replace independent variables (domain names) in julia strings with the + # corresponding symbol + for domain_name, domain_symbol in domain_name_to_symbol.items(): + all_julia_str = all_julia_str.replace( + f"grad_{domain_name}", f"D{domain_symbol}" + ) + # Different divergence depending on the coordinate system + coord_sys = getattr(pybamm.standard_spatial_vars, domain_symbol).coord_sys + if coord_sys == "cartesian": + all_julia_str = all_julia_str.replace( + f"div_{domain_name}", f"D{domain_symbol}" + ) + elif coord_sys == "spherical polar": + all_julia_str = all_julia_str.replace( + f"div_{domain_name}(", + f"1 / {domain_symbol}^2 * D{domain_symbol}({domain_symbol}^2 * ", + ) + + # Replace the thicknesses in the concatenation with the actual thickness from the + # geometry + if "neg_width" in all_julia_str or "neg_plus_sep_width" in all_julia_str: + var = pybamm.standard_spatial_vars + x_n = geometry["negative electrode"]["x_n"]["max"].evaluate() + x_s = geometry["separator"]["x_s"]["max"].evaluate() + all_julia_str = all_julia_str.replace("neg_width", str(x_n)) + all_julia_str = all_julia_str.replace("neg_plus_sep_width", str(x_s)) + + # Update the MTK string + mtk_str += all_constants_str + all_julia_str + "\n" + f"eqs = [\n{all_eqns_str}]\n" + + #################################################################################### + # Initial and boundary conditions + #################################################################################### + # Initial conditions + all_ic_bc_str = " # initial conditions\n" + all_ic_bc_constants_str = "" + all_ic_bc_julia_str = "" + for var, eqn in model.initial_conditions.items(): + ( + all_ic_bc_constants_str, + all_ic_bc_julia_str, + eqn_str, + ) = convert_var_and_eqn_to_str( + var, eqn, all_ic_bc_constants_str, all_ic_bc_julia_str, "initial condition" + ) + + if not is_pde: + all_ic_bc_str += f" {variable_to_print_name[var]}(t) => {eqn_str},\n" + else: + if var.domain == []: + doms = "" + else: + doms = ", " + domain_name_to_symbol[tuple(var.domain)] + + all_ic_bc_str += f" {variable_to_print_name[var]}(0{doms}) ~ {eqn_str},\n" + # Boundary conditions + if is_pde: + all_ic_bc_str += " # boundary conditions\n" + for var, eqn_side in model.boundary_conditions.items(): + if isinstance(var, (pybamm.Variable, pybamm.ConcatenationVariable)): + for side, (eqn, typ) in eqn_side.items(): + ( + all_ic_bc_constants_str, + all_ic_bc_julia_str, + eqn_str, + ) = convert_var_and_eqn_to_str( + var, + eqn, + all_ic_bc_constants_str, + all_ic_bc_julia_str, + "boundary condition", + ) + + if side == "left": + limit = var_to_ind_vars_left_boundary[var] + elif side == "right": + limit = var_to_ind_vars_right_boundary[var] + + bc = f"{variable_to_print_name[var]}{limit}" + if typ == "Dirichlet": + bc = bc + elif typ == "Neumann": + bc = f"D{domain_name_to_symbol[tuple(var.domain)]}({bc})" + all_ic_bc_str += f" {bc} ~ {eqn_str},\n" + + # Replace variables in the julia strings that correspond to pybamm variables with + # their julia equivalent + for var, julia_id in variable_to_print_name.items(): + # e.g. boundary_value_right(cache_123456789) gets replaced with u1(t, 1) + cache_var_id = id_to_julia_variable(var.id, "cache") + if f"boundary_value_right({cache_var_id})" in all_ic_bc_julia_str: + all_ic_bc_julia_str = all_ic_bc_julia_str.replace( + f"boundary_value_right({cache_var_id})", + julia_id + var_to_ind_vars_right_boundary[var], + ) + # e.g. cache_123456789 gets replaced with u1(t, x) + all_ic_bc_julia_str = all_ic_bc_julia_str.replace( + cache_var_id, julia_id + var_to_ind_vars[var] + ) + + #################################################################################### + + # Create ODESystem or PDESystem + if not is_pde: + mtk_str += "sys = ODESystem(eqs, t)\n\n" + mtk_str += ( + all_ic_bc_constants_str + + all_ic_bc_julia_str + + "\n" + + f"u0 = [\n{all_ic_bc_str}]\n" + ) + else: + # Initial and boundary conditions + mtk_str += ( + all_ic_bc_constants_str + + all_ic_bc_julia_str + + "\n" + + f"ics_bcs = [\n{all_ic_bc_str}]\n" + ) + + # Domains + mtk_str += "\n" + tpsan_str = ",".join( + map(lambda x: f"{x / model.timescale.evaluate():.3f}", tspan) + ) + mtk_str += f"t_domain = Interval({tpsan_str})\n" + domains = "domains = [\n t in t_domain,\n" + for domain, symbol in domain_name_to_symbol.items(): + limits = domain_name_to_limits[tuple(domain)] + if limits is not None: + mtk_str += f"{symbol}_domain = Interval{limits}\n" + domains += f" {symbol} in {symbol}_domain,\n" + domains += "]\n" + + mtk_str += "\n" + mtk_str += domains + + # Independent and dependent variables + mtk_str += "ind_vars = [{}]\n".format(", ".join(ind_vars)) + mtk_str += "dep_vars = [{}]\n\n".format(", ".join(dep_vars)) + + name = model.name.replace(" ", "_").replace("-", "_") + mtk_str += ( + name + + "_pde_system = PDESystem(eqs, ics_bcs, domains, ind_vars, dep_vars)\n\n" + ) + + # Replace parameters in the julia strings in the form "inputs[name]" + # with just "name" + for param in model.input_parameters: + mtk_str = mtk_str.replace(f"inputs['{param.name}']", param.name) + + # Need to add 'nothing' to the end of the mtk string to avoid errors in MTK v4 + # See https://github.com/SciML/diffeqpy/issues/82 + mtk_str += "nothing\nend\n" + + return mtk_str diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 6c148ce3c1..d8aa1728dc 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -38,17 +38,15 @@ def evaluate_and_test_equal( input_parameter_order = list(inputs.keys()) p = list(inputs.values()) - pybamm_eval = expr.evaluate(t=t_tests[0], y=y_tests[0], inputs=inputs).flatten() + pybamm_eval = expr.evaluate(t=t_tests[0], y=y_tests[0], inputs=inputs) for preallocate in [True, False]: kwargs["funcname"] = ( kwargs.get("funcname", "f") + "_" + str(int(preallocate)) ) - evaluator_str = pybamm.get_julia_function( - expr, - input_parameter_order=input_parameter_order, - preallocate=preallocate, - **kwargs, - ) + funcname = kwargs["funcname"] + myconverter = pybamm.JuliaConverter() + myconverter.convert_tree_to_intermediate(expr) + evaluator_str = myconverter.build_julia_code(funcname=funcname) Main.eval(evaluator_str) funcname = kwargs.get("funcname", "f") Main.p = p @@ -57,11 +55,11 @@ def evaluate_and_test_equal( Main.y = y_test Main.t = t_test try: - Main.eval(f"{funcname}!(dy,y,p,t)") + Main.eval(f"{funcname}(dy,y,p,t)") except JuliaError as e: # debugging - print(Main.dy, y_test, p, t_test) - print(evaluator_str) + #print(Main.dy, y_test, p, t_test) + #print(evaluator_str) raise e pybamm_eval = expr.evaluate(t=t_test, y=y_test, inputs=inputs).flatten() try: @@ -72,14 +70,16 @@ def evaluate_and_test_equal( ) except AssertionError as e: # debugging - print(Main.dy, y_test, p, t_test) - print(evaluator_str) + #print(Main.dy, y_test, p, t_test) + #print(evaluator_str) raise e def test_exceptions(self): a = pybamm.Symbol("a") with self.assertRaisesRegex(NotImplementedError, "Conversion to Julia"): - pybamm.get_julia_function(a) + myconverter = pybamm.JuliaConverter() + myconverter.convert_tree_to_intermediate(a) + myconverter.build_julia_code() def test_evaluator_julia(self): a = pybamm.StateVector(slice(0, 1)) @@ -308,7 +308,7 @@ def test_evaluator_julia_discretised_operators(self): y_tests = [nodes ** 2 + 1, np.cos(nodes)] for i, expr in enumerate([grad_eqn_disc, div_eqn_disc]): - self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=10) + self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=8) def test_evaluator_julia_discretised_microscale(self): # create discretisation @@ -353,7 +353,7 @@ def test_evaluator_julia_discretised_microscale(self): y_tests = [np.linspace(0, 1, total_npts) ** 2] for i, expr in enumerate([grad_eqn_disc, div_eqn_disc]): - self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=11) + self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=8) if __name__ == "__main__": From d8a5dcc7c514808bf37ceb21550de934601d398b Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 13 Sep 2022 12:25:10 -0400 Subject: [PATCH 031/163] add pybamm.negation --- .../operations/evaluate_julia.py | 36 ++++++++++++++++++- .../test_operations/test_evaluate_julia.py | 4 +++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 3dbced2d5e..dbb17c697e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -85,6 +85,9 @@ def __init__(self,name,input,output,shape): self.output = output self.shape = shape +class JuliaNegation(JuliaBroadcastableFunction): + pass + class JuliaMinimumMaximum(JuliaBroadcastableFunction): pass @@ -258,7 +261,7 @@ def get_result_variable_name(self,julia_symbol:JuliaIndex): #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. def break_down_binary(self,symbol): #Check for constant - #assert not is_constant_and_can_evaluate(symbol) + assert not is_constant_and_can_evaluate(symbol) #We know that this should only have 2 children assert len(symbol.children)==2 @@ -369,6 +372,7 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Maximum): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_the_nonscalar(id_left,id_right) self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max") + return my_id @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.Power): @@ -460,6 +464,16 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Function): my_id = symbol.id self._intermediate[my_id] = JuliaBroadcastableFunction(my_jl_name,input,my_id,my_shape) return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.Negate): + my_jl_name = "-" + assert len(symbol.children)==1 + my_shape = symbol.children[0].shape + input = self.convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaNegation(my_jl_name,input,my_id,my_shape) + return my_id @@ -666,6 +680,14 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) self._function_string+=code return 0 + + @multimethod + def convert_intermediate_to_code(self,julia_symbol:JuliaNegation): + result_var_name = self.get_result_variable_name(julia_symbol) + input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + code = "{} .= -{}\n".format(result_var_name,input_var_name) + self._function_string+=code + return 0 @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaMinimumMaximum): @@ -761,6 +783,15 @@ def write_const(self,value:scipy.sparse._csr.csr_matrix): ) return val_string + def clear(self): + self._intermediate = OrderedDict() + self._function_string = "" + self._cache_dict = OrderedDict() + self._cache_and_const_string = "" + self._const_dict = OrderedDict() + self._cache_id = 0 + self._const_id = 0 + #Just get something working here, so can start actual testing def write_function_easy(self,funcname): #start with the closure @@ -810,6 +841,9 @@ def build_julia_code(self,funcname="f"): elif type(entry) is JuliaBroadcastableFunction: self.create_cache(entry) self.convert_intermediate_to_code(entry) + elif type(entry) is JuliaNegation: + self.create_cache(entry) + self.convert_intermediate_to_code(entry) elif type(entry) is JuliaMinimumMaximum: self.create_cache(entry) self.convert_intermediate_to_code(entry) diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index d8aa1728dc..0888c59be7 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -93,6 +93,10 @@ def test_evaluator_julia(self): self.evaluate_and_test_equal(expr, np.array([2.0, 3.0])) self.evaluate_and_test_equal(expr, np.array([1.0, 3.0])) + # test negation + expr = pybamm.Negate(a * b) + self.evaluate_and_test_equal(expr, np.array([1.0, 3.0])) + # test function(a*b) expr = pybamm.cos(a * b) self.evaluate_and_test_equal(expr, np.array([1.0, 3.0]), funcname="g") From 7d57e7036f667b08e6a525a133417ca99567fe2a Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 12:03:44 -0400 Subject: [PATCH 032/163] update model code generation --- .../operations/evaluate_julia.py | 92 +++++++++++-------- pybamm/models/base_model.py | 33 +++---- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index dbb17c697e..9369703e39 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -99,10 +99,13 @@ def __init__(self,input,output,index): self.output = output self.index = index if type(index) is slice: - if type(index.step) is None: - self.shape = (slice.start-slice.stop,1) - elif type(index.step) is int: - self.shape = (floor((slice.stop-slice.start)/slice.step),1) + if index.step == None: + self.shape = ((index.stop)-(index.start),1) + elif type(index.step) == int: + self.shape = (floor((index.stop-index.start)/index.step),1) + else: + print(index.step) + raise NotImplementedError("asldhfjwaes") elif type(index) is int: self.shape = (1,1) else: @@ -182,6 +185,7 @@ def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",p self._preallocate=preallocate self._dae_type = dae_type + self._type = "Float64" #"Caches" #Stores Constants to be Declared in the initial cache #insight: everything is just a line of code @@ -236,7 +240,7 @@ def get_result_variable_name(self,julia_symbol:JuliaInput): def get_result_variable_name(self,julia_symbol:JuliaStateVector): start = julia_symbol.loc[0]+1 end = julia_symbol.loc[1] - return "(@view y[{}:{}])".format(start,end) + return "y[{}:{}]".format(start,end) @multimethod def get_result_variable_name(self,julia_symbol:JuliaBroadcastableFunction): @@ -250,9 +254,9 @@ def get_result_variable_name(self,julia_symbol:JuliaIndex): return "{}[{}]".format(lower_var,index+1) elif type(index) is slice: if index.step is None: - return "(@view {}[{}:{}])".format(lower_var,index.start+1,index.stop) + return "{}[{}:{}]".format(lower_var,index.start+1,index.stop) elif type(index.step) is int: - return "(@view {}[{}:{}:{}])".format(lower_var,index.start+1,index.step,index.stop) + return "{}[{}:{}:{}]".format(lower_var,index.start+1,index.step,index.stop) else: raise NotImplementedError("Step has to be an integer.") else: @@ -261,7 +265,7 @@ def get_result_variable_name(self,julia_symbol:JuliaIndex): #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. def break_down_binary(self,symbol): #Check for constant - assert not is_constant_and_can_evaluate(symbol) + #assert not is_constant_and_can_evaluate(symbol) #We know that this should only have 2 children assert len(symbol.children)==2 @@ -327,7 +331,7 @@ def convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) return my_id @@ -335,63 +339,63 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.Inner): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) return my_id @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.Division): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaDivision(id_left,id_right,my_id,my_shape) return my_id @multimethod def convert_tree_to_intermediate(self,symbol: pybamm.Addition): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape) return my_id @multimethod def convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape) return my_id @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.Minimum): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min") + my_shape = self.find_broadcastable_shape(id_left,id_right) + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min.") return my_id @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.Maximum): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max") + my_shape = self.find_broadcastable_shape(id_left,id_right) + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max.") return my_id @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.Power): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape) return my_id @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.EqualHeaviside): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<=") return my_id @multimethod def convert_tree_to_intermediate(self,symbol:pybamm.NotEqualHeaviside): id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_the_nonscalar(id_left,id_right) + my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<") return my_id @@ -404,18 +408,34 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Index): self._intermediate[my_id] = JuliaIndex(id_lower,my_id,index) return my_id - - - #Convenience function for operations which can have - def find_the_nonscalar(self,id_left,id_right): - left_type = type(self._intermediate[id_left]) - right_type = type(self._intermediate[id_right]) - if issubclass(left_type,JuliaScalar): - return self._intermediate[id_right].shape - elif issubclass(right_type,JuliaScalar): - return self._intermediate[id_left].shape + def find_broadcastable_shape(self,id_left,id_right): + left_shape = self._intermediate[id_left].shape + right_shape = self._intermediate[id_right].shape + #check if either is a scalar + if left_shape == (1,1): + return right_shape + elif right_shape==(1,1): + return left_shape + elif left_shape==right_shape: + return left_shape + elif (left_shape[0]==1) & (right_shape[1]==1): + return (right_shape[0],left_shape[1]) + elif (right_shape[0]==1) & (left_shape[1]==1): + return (left_shape[0],right_shape[1]) + elif (right_shape[0]==1) & (right_shape[1]==left_shape[1]): + return left_shape + elif (left_shape[0]==1) & (right_shape[1]==left_shape[1]): + return right_shape + elif (right_shape[1]==1) & (right_shape[0]==left_shape[0]): + return left_shape + elif (left_shape[1]==1) & (right_shape[0]==left_shape[0]): + return right_shape else: - return self.same_shape(id_left,id_right) + print("Right type is {}".format(type(self._intermediate[id_right]))) + print("Right Shape is {}".format(right_shape)) + print("Left Shape is {}".format(left_shape)) + raise NotImplementedError("multiplication for the shapes youve requested doesnt work.") + #to find the shape, there are a number of elements that should just have the shame shape as their children. This function removes boilerplate by implementing those cases def same_shape(self,id_left,id_right): @@ -564,9 +584,9 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaConcatenation): elif child_var.shape[0] == 1: end_row = 1 if vec: - code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: - code = "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "{}[{}{} = (@view {})\n".format(my_name,start_row,right_parenthesis,child_var_name) else: start_row = 1 end_row = child_var.shape[0] @@ -616,12 +636,11 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaDomainConcatenation): stop = this_slice.stop start_row = end_row+1 end_row = start_row+(stop-start)-1 - code += "{}[{}:{}{} .= {}[{}:{}{}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + code += "{}[{}:{}{} .= (@view {}[{}:{}{})\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) self._function_string+=code return 0 - @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): @@ -774,9 +793,10 @@ def write_const(self,value:scipy.sparse._csr.csr_matrix): threshold=max(np.get_printoptions()["threshold"], len(row) + 10) ) - val_string = "sparse({}, {}, {}, {}, {})".format( + val_string = "sparse({}, {}, {}{}, {}, {})".format( np.array2string(row + 1, separator=","), np.array2string(col + 1, separator=","), + self._type, np.array2string(data, separator=","), m, n, diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index f8196d68c1..6568aafe53 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1147,16 +1147,13 @@ def generate_julia_diffeq( len_rhs = None else: len_rhs = self.concatenated_rhs.size + raise NotImplementedError("can't do this yet") # DAE model: form out[] = ... - dy[] - eqn_str = pybamm.get_julia_function( - pybamm.numpy_concatenation( - self.concatenated_rhs, self.concatenated_algebraic - ), - funcname=name, - input_parameter_order=input_parameter_order, - len_rhs=len_rhs, - **kwargs, + converter = pybamm.JuliaConverter() + converter.convert_tree_to_intermediate( + pybamm.numpy_concatenation(self.concatenated_rhs, self.concatenated_algebraic) ) + eqn_str = converter.build_julia_code(funcname=name) if get_consistent_ics_solver is None or self.algebraic == {}: ics = self.concatenated_initial_conditions @@ -1164,13 +1161,9 @@ def generate_julia_diffeq( get_consistent_ics_solver.set_up(self) get_consistent_ics_solver._set_initial_conditions(self, {}, False) ics = pybamm.Vector(self.y0.full()) - - ics_str = pybamm.get_julia_function( - ics, - funcname=name + "_u0", - input_parameter_order=input_parameter_order, - **kwargs, - ) + ics_converter = pybamm.JuliaConverter() + ics_converter.convert_tree_to_intermediate(ics) + ics_str = ics_converter.build_julia_code(funcname=name+"_u0") # Change the string to a form for u0 ics_str = ics_str.replace("(dy, y, p, t)", "(u0, p)") ics_str = ics_str.replace("dy", "u0") @@ -1179,13 +1172,9 @@ def generate_julia_diffeq( size_state = self.concatenated_initial_conditions.size state_vector = pybamm.StateVector(slice(0,(size_state-1))) expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) - jac_str = pybamm.get_julia_function( - expr, - funcname="jac_"+name, - input_parameter_order=input_parameter_order, - **kwargs, - ) - jac_str.replace("dy","J") + jac_converter = pybamm.JuliaConverter() + jac_converter.convert_tree_to_intermediate(expr) + jac_str = jac_converter.build_julia_code(funcname="jac_"+name) return eqn_str,ics_str,jac_str return eqn_str, ics_str From e4f3a85b5f23f10dc384e245826618cd08b519a7 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 12:16:40 -0400 Subject: [PATCH 033/163] spaces --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 9369703e39..25591d40aa 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -831,10 +831,10 @@ def write_function_easy(self,funcname): self._function_string = parameter_string + self._function_string if my_shape[1] != 1: self._function_string += "J[:,:] .= {}\nend\nend".format(top_var_name) - self._function_string = "function {}(J,y,p,t)\n".format(funcname) + self._function_string + self._function_string = "function {}(J, y, p, t)\n".format(funcname) + self._function_string else: self._function_string+= "dy[:] .= {}\nend\nend".format(top_var_name) - self._function_string = "function {}(dy,y,p,t)\n".format(funcname) + self._function_string + self._function_string = "function {}(dy, y, p, t)\n".format(funcname) + self._function_string From 016cb94a37008062d30b165307755154b9dce3f8 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 13:12:48 -0400 Subject: [PATCH 034/163] added state vector time-derivative --- .../operations/evaluate_julia.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 25591d40aa..22e352378e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -129,6 +129,9 @@ def __init__(self,id,loc,shape): self.loc = loc self.shape = shape +class JuliaStateVectorDot(JuliaStateVector): + pass + class JuliaScalar(JuliaConstant): def __init__(self,id,value): self.id = id @@ -242,6 +245,12 @@ def get_result_variable_name(self,julia_symbol:JuliaStateVector): end = julia_symbol.loc[1] return "y[{}:{}]".format(start,end) + @multimethod + def get_result_variable_name(self,julia_symbol:JuliaStateVectorDot): + start = julia_symbol.loc[0]+1 + end = julia_symbol.loc[1] + return "dy[{}:{}]".format(start,end) + @multimethod def get_result_variable_name(self,julia_symbol:JuliaBroadcastableFunction): return self._cache_dict[julia_symbol.output] @@ -551,6 +560,16 @@ def convert_tree_to_intermediate(self,symbol:pybamm.StateVector): shape = symbol.shape self._intermediate[my_id] = JuliaStateVector(id,points,shape) return my_id + + @multimethod + def convert_tree_to_intermediate(self,symbol:pybamm.StateVectorDot): + my_id = symbol.id + first_point = symbol.first_point + last_point = symbol.last_point + points = (first_point,last_point) + shape = symbol.shape + self._intermediate[my_id] = JuliaStateVectorDot(id,points,shape) + return my_id #utilities for code conversion def get_variables_for_binary_tree(self,julia_symbol): @@ -856,6 +875,8 @@ def build_julia_code(self,funcname="f"): continue elif type(entry) is JuliaStateVector: continue + elif type(entry) is JuliaStateVectorDot: + continue elif type(entry) is JuliaScalar: continue elif type(entry) is JuliaBroadcastableFunction: From f65359d9720a2b94017153a3769605d2980cabc9 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 13:28:09 -0400 Subject: [PATCH 035/163] working on adding dae --- .../operations/evaluate_julia.py | 88 +++++++++++-------- pybamm/models/base_model.py | 11 ++- 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 22e352378e..1d77ad9d59 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -280,15 +280,15 @@ def break_down_binary(self,symbol): assert len(symbol.children)==2 #take care of the kids first (this is recursive but multiple-dispatch recursive which is cool) - id_left = self.convert_tree_to_intermediate(symbol.children[0]) - id_right = self.convert_tree_to_intermediate(symbol.children[1]) + id_left = self._convert_tree_to_intermediate(symbol.children[0]) + id_right = self._convert_tree_to_intermediate(symbol.children[1]) my_id = symbol.id return id_left,id_right,my_id def break_down_concatenation(self,symbol): child_ids = [] for child in symbol.children: - child_id = self.convert_tree_to_intermediate(child) + child_id = self._convert_tree_to_intermediate(child) child_ids.append(child_id) first_id = child_ids[0] num_cols = self._intermediate[first_id].shape[1] @@ -305,21 +305,21 @@ def break_down_concatenation(self,symbol): #Binary trees constructors. All follow the pattern of mat-mul. They need to find their shapes, assuming that the shapes of the nodes one level below them in the expression tree have already been computed. @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.NumpyConcatenation): + def _convert_tree_to_intermediate(self,symbol:pybamm.NumpyConcatenation): my_id = symbol.id children_julia,shape = self.break_down_concatenation(symbol) self._intermediate[my_id] = JuliaNumpyConcatenation(my_id,shape,children_julia) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.SparseStack): + def _convert_tree_to_intermediate(self,symbol:pybamm.SparseStack): my_id = symbol.id children_julia,shape = self.break_down_concatenation(symbol) self._intermediate[my_id] = JuliaSparseStack(my_id,shape,children_julia) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.DomainConcatenation): + def _convert_tree_to_intermediate(self,symbol:pybamm.DomainConcatenation): my_id = symbol.id children_julia,shape = self.break_down_concatenation(symbol) self._intermediate[my_id] = JuliaDomainConcatenation(my_id,shape,children_julia,symbol.secondary_dimensions_npts,symbol._children_slices) @@ -327,7 +327,7 @@ def convert_tree_to_intermediate(self,symbol:pybamm.DomainConcatenation): @multimethod - def convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): + def _convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): #Break down the binary tree id_left,id_right,my_id = self.break_down_binary(symbol) left_shape = self._intermediate[id_left].shape @@ -338,7 +338,7 @@ def convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): + def _convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) @@ -346,72 +346,72 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): #Apparently an inner product is a hadamard product in pybamm @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Inner): + def _convert_tree_to_intermediate(self,symbol:pybamm.Inner): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Division): + def _convert_tree_to_intermediate(self,symbol:pybamm.Division): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaDivision(id_left,id_right,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol: pybamm.Addition): + def _convert_tree_to_intermediate(self,symbol: pybamm.Addition): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): + def _convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Minimum): + def _convert_tree_to_intermediate(self,symbol:pybamm.Minimum): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min.") return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Maximum): + def _convert_tree_to_intermediate(self,symbol:pybamm.Maximum): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max.") return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Power): + def _convert_tree_to_intermediate(self,symbol:pybamm.Power): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.EqualHeaviside): + def _convert_tree_to_intermediate(self,symbol:pybamm.EqualHeaviside): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<=") return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.NotEqualHeaviside): + def _convert_tree_to_intermediate(self,symbol:pybamm.NotEqualHeaviside): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<") return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Index): + def _convert_tree_to_intermediate(self,symbol:pybamm.Index): assert len(symbol.children)==1 - id_lower = self.convert_tree_to_intermediate(symbol.children[0]) + id_lower = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id index = symbol.index self._intermediate[my_id] = JuliaIndex(id_lower,my_id,index) @@ -457,7 +457,7 @@ def same_shape(self,id_left,id_right): #Functions #Broadcastable functions have 1 input and 1 output, and the input and output have the same shape. The hard part is that we have to know which is which and pybamm doesn't differentiate between the two. So, we have to do that with an if statement. @multimethod - def convert_tree_to_intermediate(self,symbol): + def _convert_tree_to_intermediate(self,symbol): raise NotImplementedError( "Conversion to Julia not implemented for a symbol of type '{}'".format( type(symbol) @@ -465,41 +465,41 @@ def convert_tree_to_intermediate(self,symbol): ) @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Min): + def _convert_tree_to_intermediate(self,symbol:pybamm.Min): my_jl_name = symbol.julia_name assert len(symbol.children)==1 my_shape = (1,1) - input = self.convert_tree_to_intermediate(symbol.children[0]) + input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id self._intermediate[my_id] = JuliaMinimumMaximum(my_jl_name,input,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Max): + def _convert_tree_to_intermediate(self,symbol:pybamm.Max): my_jl_name = symbol.julia_name assert len(symbol.children)==1 my_shape = (1,1) - input = self.convert_tree_to_intermediate(symbol.children[0]) + input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id self._intermediate[my_id] = JuliaMinimumMaximum(my_jl_name,input,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Function): + def _convert_tree_to_intermediate(self,symbol:pybamm.Function): my_jl_name = symbol.julia_name assert len(symbol.children)==1 my_shape = symbol.children[0].shape - input = self.convert_tree_to_intermediate(symbol.children[0]) + input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id self._intermediate[my_id] = JuliaBroadcastableFunction(my_jl_name,input,my_id,my_shape) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Negate): + def _convert_tree_to_intermediate(self,symbol:pybamm.Negate): my_jl_name = "-" assert len(symbol.children)==1 my_shape = symbol.children[0].shape - input = self.convert_tree_to_intermediate(symbol.children[0]) + input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id self._intermediate[my_id] = JuliaNegation(my_jl_name,input,my_id,my_shape) return my_id @@ -508,7 +508,7 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Negate): #Constants and Values. There are only 2 of these. They must know their own shapes. @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Matrix): + def _convert_tree_to_intermediate(self,symbol:pybamm.Matrix): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() @@ -519,7 +519,7 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Matrix): return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Vector): + def _convert_tree_to_intermediate(self,symbol:pybamm.Vector): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() @@ -530,7 +530,7 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Vector): return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Scalar): + def _convert_tree_to_intermediate(self,symbol:pybamm.Scalar): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() @@ -538,13 +538,13 @@ def convert_tree_to_intermediate(self,symbol:pybamm.Scalar): return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.Time): + def _convert_tree_to_intermediate(self,symbol:pybamm.Time): my_id = symbol.id self._intermediate[my_id] = JuliaTime(my_id) return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.InputParameter): + def _convert_tree_to_intermediate(self,symbol:pybamm.InputParameter): my_id = symbol.id name = symbol.name self._intermediate[my_id] = JuliaInput(my_id,name) @@ -552,7 +552,7 @@ def convert_tree_to_intermediate(self,symbol:pybamm.InputParameter): return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.StateVector): + def _convert_tree_to_intermediate(self,symbol:pybamm.StateVector): my_id = symbol.id first_point = symbol.first_point last_point = symbol.last_point @@ -562,7 +562,7 @@ def convert_tree_to_intermediate(self,symbol:pybamm.StateVector): return my_id @multimethod - def convert_tree_to_intermediate(self,symbol:pybamm.StateVectorDot): + def _convert_tree_to_intermediate(self,symbol:pybamm.StateVectorDot): my_id = symbol.id first_point = symbol.first_point last_point = symbol.last_point @@ -860,8 +860,22 @@ def write_function_easy(self,funcname): return 0 - - + #this function will be the top level. + def convert_tree_to_intermediate(self,symbol): + if self._dae_type == "implicit": + len_rhs = symbol.concatenated_rhs.size + symbol_minus_dy = [] + end = 0 + for child in symbol.orphans: + start = end + end += child.size + if end <= len_rhs: + symbol_minus_dy.append(child - pybamm.StateVectorDot(slice(start, end))) + else: + symbol_minus_dy.append(child) + symbol = pybamm.numpy_concatenation(*symbol_minus_dy) + self._convert_tree_to_intermediate(symbol) + return 0 #rework this at some point def build_julia_code(self,funcname="f"): diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 6568aafe53..539b1e151a 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1147,7 +1147,16 @@ def generate_julia_diffeq( len_rhs = None else: len_rhs = self.concatenated_rhs.size - raise NotImplementedError("can't do this yet") + symbol_minus_dy = [] + end = 0 + for child in symbol.orphans: + start = end + end += child.size + if end <= len_rhs: + symbol_minus_dy.append(child - pybamm.StateVectorDot(slice(start, end))) + else: + symbol_minus_dy.append(child) + symbol = pybamm.numpy_concatenation(*symbol_minus_dy) # DAE model: form out[] = ... - dy[] converter = pybamm.JuliaConverter() converter.convert_tree_to_intermediate( From 4b2059464a1c0588a308810ebb35fec75640c10f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 13:35:56 -0400 Subject: [PATCH 036/163] update for daes --- pybamm/models/base_model.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 539b1e151a..f85ca86399 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1136,20 +1136,20 @@ def generate_julia_diffeq( if self.algebraic == {}: # ODE model: form dy[] = ... - eqn_str = pybamm.get_julia_function( - self.concatenated_rhs, - funcname=name, - input_parameter_order=input_parameter_order, - **kwargs, - ) + converter = pybamm.JuliaConverter() + converter.convert_tree_to_intermediate(self.concatenated_rhs) + eqn_str = converter.build_julia_code(funcname=name) else: if dae_type == "semi-explicit": len_rhs = None + converter = pybamm.JuliaConverter() + converter.convert_tree_to_intermediate(self.concatenated_rhs) + eqn_str = converter.build_julia_code(funcname=name) else: len_rhs = self.concatenated_rhs.size symbol_minus_dy = [] end = 0 - for child in symbol.orphans: + for child in self.orphans: start = end end += child.size if end <= len_rhs: @@ -1157,12 +1157,9 @@ def generate_julia_diffeq( else: symbol_minus_dy.append(child) symbol = pybamm.numpy_concatenation(*symbol_minus_dy) - # DAE model: form out[] = ... - dy[] - converter = pybamm.JuliaConverter() - converter.convert_tree_to_intermediate( - pybamm.numpy_concatenation(self.concatenated_rhs, self.concatenated_algebraic) - ) - eqn_str = converter.build_julia_code(funcname=name) + converter = pybamm.JuliaConverter(dae_type="implicit") + converter.convert_tree_to_intermediate(symbol) + eqn_str = converter.build_julia_code() if get_consistent_ics_solver is None or self.algebraic == {}: ics = self.concatenated_initial_conditions From 42c6da6bac1a3aceb5e518770285163d5cc224d6 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 13:59:07 -0400 Subject: [PATCH 037/163] fix issues --- .../operations/evaluate_julia.py | 4 ++-- pybamm/models/base_model.py | 20 ++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 1d77ad9d59..d60e9208a2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -861,9 +861,9 @@ def write_function_easy(self,funcname): #this function will be the top level. - def convert_tree_to_intermediate(self,symbol): + def convert_tree_to_intermediate(self,symbol,len_rhs=None): if self._dae_type == "implicit": - len_rhs = symbol.concatenated_rhs.size + assert len_rhs != None symbol_minus_dy = [] end = 0 for child in symbol.orphans: diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index f85ca86399..bd61fecba9 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1142,24 +1142,12 @@ def generate_julia_diffeq( else: if dae_type == "semi-explicit": len_rhs = None - converter = pybamm.JuliaConverter() - converter.convert_tree_to_intermediate(self.concatenated_rhs) - eqn_str = converter.build_julia_code(funcname=name) else: len_rhs = self.concatenated_rhs.size - symbol_minus_dy = [] - end = 0 - for child in self.orphans: - start = end - end += child.size - if end <= len_rhs: - symbol_minus_dy.append(child - pybamm.StateVectorDot(slice(start, end))) - else: - symbol_minus_dy.append(child) - symbol = pybamm.numpy_concatenation(*symbol_minus_dy) - converter = pybamm.JuliaConverter(dae_type="implicit") - converter.convert_tree_to_intermediate(symbol) - eqn_str = converter.build_julia_code() + + converter = pybamm.JuliaConverter(dae_type=dae_type) + converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(pybamm.concatenated_rhs,pybamm.concatenated_algebraic),len_rhs=len_rhs) + eqn_string = converter.build_julia_code(funcname=name) if get_consistent_ics_solver is None or self.algebraic == {}: ics = self.concatenated_initial_conditions From 50f7880fcc43c9a7ac045906a40a7ffc6523e0dd Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:01:28 -0400 Subject: [PATCH 038/163] typo --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index bd61fecba9..2c60aa3075 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1146,7 +1146,7 @@ def generate_julia_diffeq( len_rhs = self.concatenated_rhs.size converter = pybamm.JuliaConverter(dae_type=dae_type) - converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(pybamm.concatenated_rhs,pybamm.concatenated_algebraic),len_rhs=len_rhs) + converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic),len_rhs=len_rhs) eqn_string = converter.build_julia_code(funcname=name) if get_consistent_ics_solver is None or self.algebraic == {}: From f72b4e514723a4aa2d59aae568a144293694423b Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:03:42 -0400 Subject: [PATCH 039/163] typo --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 2c60aa3075..786de1026e 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1147,7 +1147,7 @@ def generate_julia_diffeq( converter = pybamm.JuliaConverter(dae_type=dae_type) converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic),len_rhs=len_rhs) - eqn_string = converter.build_julia_code(funcname=name) + eqn_str = converter.build_julia_code(funcname=name) if get_consistent_ics_solver is None or self.algebraic == {}: ics = self.concatenated_initial_conditions From 3358affdcfb7d2ca0ed3589c85f1664b29426428 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:22:56 -0400 Subject: [PATCH 040/163] add function signatures for dae --- pybamm/expression_tree/operations/evaluate_julia.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index d60e9208a2..c36360346e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -849,11 +849,14 @@ def write_function_easy(self,funcname): parameter_string += "= p\n" self._function_string = parameter_string + self._function_string if my_shape[1] != 1: - self._function_string += "J[:,:] .= {}\nend\nend".format(top_var_name) + self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend".format(top_var_name) self._function_string = "function {}(J, y, p, t)\n".format(funcname) + self._function_string - else: - self._function_string+= "dy[:] .= {}\nend\nend".format(top_var_name) + elif self._dae_type=="semi-explicit": + self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend".format(top_var_name) self._function_string = "function {}(dy, y, p, t)\n".format(funcname) + self._function_string + elif self._dae_type=="implicit": + self._function_string+="out[:] .= {}\nreturn nothing\nend\nend".format(top_var_name) + self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname) + self._function_string From c11341e58dc937de92258225ec0a8ef1cb9775a1 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:33:01 -0400 Subject: [PATCH 041/163] update cache to not create scalar --- .../operations/evaluate_julia.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c36360346e..cc18641b2c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -771,20 +771,21 @@ def create_cache(self,symbol): my_id = symbol.output cache_shape = self._intermediate[my_id].shape - cache_id = self._cache_id+1 self._cache_id = cache_id - cache_name = "cache_{}".format(cache_id) - self._cache_dict[symbol.output] = "cs."+cache_name - - if self._cache_type=="standard": - if cache_shape[1] == 1: - cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) + if cache_shape!=(1,1): + self._cache_dict[symbol.output] = "cs."+cache_name + if self._cache_type=="standard": + if cache_shape[1] == 1: + cache_shape = "({})".format(cache_shape[0]) + self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) + else: + raise NotImplementedError("The cache type you've specified has not yet been implemented") return 0 else: - raise NotImplementedError("uh oh") + self._cache_dict[symbol.output] = cache_name + return 0 From b36e04af6a35823599a0f8f676e215695a7737c4 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:41:49 -0400 Subject: [PATCH 042/163] add scalar cases --- .../operations/evaluate_julia.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index cc18641b2c..81a5ae77c7 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -674,6 +674,9 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if julia_symbol.size==(1,1): + code = "{} = {} + {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + return 0 if self._preallocate: code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: @@ -684,6 +687,9 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if julia_symbol.size==(1,1): + code = "{} = {} - {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + return 0 if self._preallocate: code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: @@ -694,6 +700,9 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if julia_symbol.size==(1,1): + code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + return 0 if self._preallocate: code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: @@ -704,6 +713,9 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) + if julia_symbol.size==(1,1): + code = "{} = {} / {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + return 0 if self._preallocate: code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: @@ -715,6 +727,9 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): result_var_name = self.get_result_variable_name(julia_symbol) input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + if julia_symbol.size==(1,1): + code = "{} = {}({})\n".format(result_var_name,julia_symbol.name,input_var_name) + return 0 code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) self._function_string+=code return 0 @@ -723,6 +738,9 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): def convert_intermediate_to_code(self,julia_symbol:JuliaNegation): result_var_name = self.get_result_variable_name(julia_symbol) input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) + if julia_symbol.size==(1,1): + code = "{} = -{}\n".format(result_var_name,input_var_name) + return 0 code = "{} .= -{}\n".format(result_var_name,input_var_name) self._function_string+=code return 0 From 6d12860c7f859961cc6ee03ec5a8da7d45915ed4 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:43:42 -0400 Subject: [PATCH 043/163] typo --- pybamm/expression_tree/operations/evaluate_julia.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 81a5ae77c7..a615a334f9 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -674,7 +674,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.size==(1,1): + if julia_symbol.shape==(1,1): code = "{} = {} + {}\n".format(result_var_name,left_input_var_name,right_input_var_name) return 0 if self._preallocate: @@ -687,7 +687,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.size==(1,1): + if julia_symbol.shape==(1,1): code = "{} = {} - {}\n".format(result_var_name,left_input_var_name,right_input_var_name) return 0 if self._preallocate: @@ -700,7 +700,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.size==(1,1): + if julia_symbol.shape==(1,1): code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) return 0 if self._preallocate: @@ -713,7 +713,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.size==(1,1): + if julia_symbol.shape==(1,1): code = "{} = {} / {}\n".format(result_var_name,left_input_var_name,right_input_var_name) return 0 if self._preallocate: @@ -727,7 +727,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): result_var_name = self.get_result_variable_name(julia_symbol) input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - if julia_symbol.size==(1,1): + if julia_symbol.shape==(1,1): code = "{} = {}({})\n".format(result_var_name,julia_symbol.name,input_var_name) return 0 code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) @@ -738,7 +738,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): def convert_intermediate_to_code(self,julia_symbol:JuliaNegation): result_var_name = self.get_result_variable_name(julia_symbol) input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - if julia_symbol.size==(1,1): + if julia_symbol.shape==(1,1): code = "{} = -{}\n".format(result_var_name,input_var_name) return 0 code = "{} .= -{}\n".format(result_var_name,input_var_name) From c1eea21049d7317e35ab963c8111ec1707d3d807 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:49:59 -0400 Subject: [PATCH 044/163] typo --- .../operations/evaluate_julia.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index a615a334f9..2b3f2eff33 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -676,11 +676,11 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) if julia_symbol.shape==(1,1): code = "{} = {} + {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - return 0 - if self._preallocate: - code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @@ -689,11 +689,11 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) if julia_symbol.shape==(1,1): code = "{} = {} - {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - return 0 - if self._preallocate: - code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @@ -702,11 +702,11 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) if julia_symbol.shape==(1,1): code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - return 0 - if self._preallocate: - code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @@ -715,11 +715,11 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) if julia_symbol.shape==(1,1): code = "{} = {} / {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - return 0 - if self._preallocate: - code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @@ -729,8 +729,8 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) if julia_symbol.shape==(1,1): code = "{} = {}({})\n".format(result_var_name,julia_symbol.name,input_var_name) - return 0 - code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) + else: + code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) self._function_string+=code return 0 @@ -740,8 +740,8 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaNegation): input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) if julia_symbol.shape==(1,1): code = "{} = -{}\n".format(result_var_name,input_var_name) - return 0 - code = "{} .= -{}\n".format(result_var_name,input_var_name) + else: + code = "{} .= -{}\n".format(result_var_name,input_var_name) self._function_string+=code return 0 From af50f14fbe2706e2fe5ce1e3e84097d0a66d8ac8 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 14:56:08 -0400 Subject: [PATCH 045/163] properly handle state vector scalars --- pybamm/expression_tree/operations/evaluate_julia.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 2b3f2eff33..6023afa0d0 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -243,13 +243,19 @@ def get_result_variable_name(self,julia_symbol:JuliaInput): def get_result_variable_name(self,julia_symbol:JuliaStateVector): start = julia_symbol.loc[0]+1 end = julia_symbol.loc[1] - return "y[{}:{}]".format(start,end) + if start==end: + return "y[{}]".format(start) + else: + return "y[{}:{}]".format(start,end) @multimethod def get_result_variable_name(self,julia_symbol:JuliaStateVectorDot): start = julia_symbol.loc[0]+1 end = julia_symbol.loc[1] - return "dy[{}:{}]".format(start,end) + if start==end: + return "dy[{}]".format(start) + else: + return "dy[{}:{}]".format(start,end) @multimethod def get_result_variable_name(self,julia_symbol:JuliaBroadcastableFunction): From 3575c17caf28d247bc9857f2af6d98aca98c5e09 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 15:02:33 -0400 Subject: [PATCH 046/163] properly handle state vector scalars --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 6023afa0d0..c5b8954b38 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -798,7 +798,7 @@ def create_cache(self,symbol): cache_id = self._cache_id+1 self._cache_id = cache_id cache_name = "cache_{}".format(cache_id) - if cache_shape!=(1,1): + if (cache_shape!=(1,1)) | (type(symbol)==JuliaMatrixMultiplication): self._cache_dict[symbol.output] = "cs."+cache_name if self._cache_type=="standard": if cache_shape[1] == 1: From ba194aa8ff4bd8de166eeb299313621709409c30 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 15:15:02 -0400 Subject: [PATCH 047/163] properly handle state vector scalars --- pybamm/expression_tree/operations/evaluate_julia.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c5b8954b38..286ff07b64 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -670,10 +670,13 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaDomainConcatenation): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) + if julia_symbol.shape==(1,1): + code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @@ -798,7 +801,7 @@ def create_cache(self,symbol): cache_id = self._cache_id+1 self._cache_id = cache_id cache_name = "cache_{}".format(cache_id) - if (cache_shape!=(1,1)) | (type(symbol)==JuliaMatrixMultiplication): + if (cache_shape!=(1,1)): self._cache_dict[symbol.output] = "cs."+cache_name if self._cache_type=="standard": if cache_shape[1] == 1: From 1414a0765aeefb0dcf84d31673a760702e429484 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 15:26:33 -0400 Subject: [PATCH 048/163] properly handle state vector scalars --- .../operations/evaluate_julia.py | 55 ++++++------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 286ff07b64..52f5adcc40 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -670,65 +670,50 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaDomainConcatenation): @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.shape==(1,1): - code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - if self._preallocate: - code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) + code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.shape==(1,1): - code = "{} = {} + {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - if self._preallocate: - code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) + code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.shape==(1,1): - code = "{} = {} - {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - if self._preallocate: - code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) + code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.shape==(1,1): - code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - if self._preallocate: - code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) + code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @multimethod def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if julia_symbol.shape==(1,1): - code = "{} = {} / {}\n".format(result_var_name,left_input_var_name,right_input_var_name) + if self._preallocate: + code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - if self._preallocate: - code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) + code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) self._function_string+=code return 0 @@ -736,10 +721,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): result_var_name = self.get_result_variable_name(julia_symbol) input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - if julia_symbol.shape==(1,1): - code = "{} = {}({})\n".format(result_var_name,julia_symbol.name,input_var_name) - else: - code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) + code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) self._function_string+=code return 0 @@ -747,10 +729,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): def convert_intermediate_to_code(self,julia_symbol:JuliaNegation): result_var_name = self.get_result_variable_name(julia_symbol) input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - if julia_symbol.shape==(1,1): - code = "{} = -{}\n".format(result_var_name,input_var_name) - else: - code = "{} .= -{}\n".format(result_var_name,input_var_name) + code = "{} .= -{}\n".format(result_var_name,input_var_name) self._function_string+=code return 0 From 03bb38c27839a8017ca578a6a600854826bbd524 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 15:27:43 -0400 Subject: [PATCH 049/163] properly handle state vector scalars --- .../operations/evaluate_julia.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 52f5adcc40..1fbabcb663 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -780,18 +780,14 @@ def create_cache(self,symbol): cache_id = self._cache_id+1 self._cache_id = cache_id cache_name = "cache_{}".format(cache_id) - if (cache_shape!=(1,1)): - self._cache_dict[symbol.output] = "cs."+cache_name - if self._cache_type=="standard": - if cache_shape[1] == 1: - cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) - else: - raise NotImplementedError("The cache type you've specified has not yet been implemented") - return 0 + self._cache_dict[symbol.output] = "cs."+cache_name + if self._cache_type=="standard": + if cache_shape[1] == 1: + cache_shape = "({})".format(cache_shape[0]) + self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) else: - self._cache_dict[symbol.output] = cache_name - return 0 + raise NotImplementedError("The cache type you've specified has not yet been implemented") + return 0 From 40abe1ccd221d5234b0de73af501e67a5d303dea Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 15:37:28 -0400 Subject: [PATCH 050/163] properly handle state vector scalars --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 1fbabcb663..5e595a617b 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -609,7 +609,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaConcatenation): elif child_var.shape[0] == 1: end_row = 1 if vec: - code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: code = "{}[{}{} = (@view {})\n".format(my_name,start_row,right_parenthesis,child_var_name) else: From 50eb7da446336cb85a5d8e5797a179af1cbbbdee Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 14 Sep 2022 15:44:04 -0400 Subject: [PATCH 051/163] properly handle state vector scalars --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 5e595a617b..6d1f4a2bcf 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -609,7 +609,7 @@ def convert_intermediate_to_code(self,julia_symbol:JuliaConcatenation): elif child_var.shape[0] == 1: end_row = 1 if vec: - code = "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) else: code = "{}[{}{} = (@view {})\n".format(my_name,start_row,right_parenthesis,child_var_name) else: From 4877d3055c2dd809cee1fae46107bdb45f22b53c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 14:15:00 -0400 Subject: [PATCH 052/163] update parameter handling --- pybamm/expression_tree/operations/evaluate_julia.py | 11 +++++------ pybamm/models/base_model.py | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 6d1f4a2bcf..72735b4884 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -178,7 +178,7 @@ def __init__(self,output,shape,children,secondary_dimension_npts,children_slices class JuliaConverter(object): - def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit"): + def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit",input_parameter_order=[]): assert not ismtk #Characteristics @@ -200,7 +200,7 @@ def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",p self._cache_dict = OrderedDict() self._const_dict = OrderedDict() - self._parameter_dict = OrderedDict() + self.input_parameter_order = input_parameter_order self._cache_id = 0 self._const_id = 0 @@ -554,7 +554,6 @@ def _convert_tree_to_intermediate(self,symbol:pybamm.InputParameter): my_id = symbol.id name = symbol.name self._intermediate[my_id] = JuliaInput(my_id,name) - self._parameter_dict[my_id] = name return my_id @multimethod @@ -844,10 +843,10 @@ def write_function_easy(self,funcname): top = self._intermediate[next(reversed(self._intermediate))] top_var_name = self.get_result_variable_name(top) my_shape = top.shape - if len(self._parameter_dict) != 0: + if len(self.input_parameter_order) != 0: parameter_string = "" - for parameter in self._parameter_dict.items(): - parameter_string+="{},".format(parameter[1]) + for parameter in self.input_parameter_order: + parameter_string+="{},".format(parameter) parameter_string = parameter_string[0:-1] parameter_string += "= p\n" self._function_string = parameter_string + self._function_string diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 786de1026e..09776afb0b 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1136,7 +1136,7 @@ def generate_julia_diffeq( if self.algebraic == {}: # ODE model: form dy[] = ... - converter = pybamm.JuliaConverter() + converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order) converter.convert_tree_to_intermediate(self.concatenated_rhs) eqn_str = converter.build_julia_code(funcname=name) else: @@ -1145,7 +1145,7 @@ def generate_julia_diffeq( else: len_rhs = self.concatenated_rhs.size - converter = pybamm.JuliaConverter(dae_type=dae_type) + converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order) converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic),len_rhs=len_rhs) eqn_str = converter.build_julia_code(funcname=name) @@ -1155,7 +1155,7 @@ def generate_julia_diffeq( get_consistent_ics_solver.set_up(self) get_consistent_ics_solver._set_initial_conditions(self, {}, False) ics = pybamm.Vector(self.y0.full()) - ics_converter = pybamm.JuliaConverter() + ics_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order) ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name+"_u0") # Change the string to a form for u0 From 50f99cae8f64c2e91c2643beb5d6e13936e53ab4 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 14:52:30 -0400 Subject: [PATCH 053/163] switch default input parameter order --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 09776afb0b..ec2fa17651 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1101,7 +1101,7 @@ def generate( def generate_julia_diffeq( self, - input_parameter_order=None, + input_parameter_order=[], get_consistent_ics_solver=None, dae_type="semi-explicit", generate_jacobian=False, From 6a15300fd1da288cf0ee8aa52df805b50f071df5 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:08:55 -0400 Subject: [PATCH 054/163] add dualcache --- pybamm/expression_tree/operations/evaluate_julia.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 72735b4884..70de630165 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -209,6 +209,7 @@ def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",p self.function_definition = "" self._function_string = "" self._return_string = "" + self._cache_initialization_string = "" #know where to go to find a variable. this could be smoother, there will need to be a ton of boilerplate here. @multimethod @@ -784,7 +785,12 @@ def create_cache(self,symbol): if cache_shape[1] == 1: cache_shape = "({})".format(cache_shape[0]) self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) - else: + elif self._cache_type=="dual": + if cache_shape[1] == 1: + cache_shape = "({})".format(cache_shape[0]) + self._cache_and_const_string+="{} = dualcache(zeros({}),12)".format(cache_shape) + self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + raise NotImplementedError("The cache type you've specified has not yet been implemented") return 0 From dc02f97ce282962ad810ad50fc7d20bbf7e2a76c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:11:24 -0400 Subject: [PATCH 055/163] add symcache --- pybamm/expression_tree/operations/evaluate_julia.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 70de630165..00388adef3 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -790,7 +790,11 @@ def create_cache(self,symbol): cache_shape = "({})".format(cache_shape[0]) self._cache_and_const_string+="{} = dualcache(zeros({}),12)".format(cache_shape) self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) - + elif self._cache_type=="symbolic": + if cache_shape[1]==1: + cache_shape = "({})".format(cache_shape[0]) + self._cache_and_const_string+=" {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape[0],cache_shape[0]) + else: raise NotImplementedError("The cache type you've specified has not yet been implemented") return 0 From 6e3a0b63ae2987c3a2a296a8e6cb436e7c32ba95 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:23:21 -0400 Subject: [PATCH 056/163] propagate dualcache --- pybamm/models/base_model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index ec2fa17651..d5c90fc0ba 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1105,6 +1105,7 @@ def generate_julia_diffeq( get_consistent_ics_solver=None, dae_type="semi-explicit", generate_jacobian=False, + cache_type="standard", **kwargs, ): """ @@ -1136,7 +1137,7 @@ def generate_julia_diffeq( if self.algebraic == {}: # ODE model: form dy[] = ... - converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order) + converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type) converter.convert_tree_to_intermediate(self.concatenated_rhs) eqn_str = converter.build_julia_code(funcname=name) else: @@ -1145,7 +1146,7 @@ def generate_julia_diffeq( else: len_rhs = self.concatenated_rhs.size - converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order) + converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order,cache_type=cache_type) converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic),len_rhs=len_rhs) eqn_str = converter.build_julia_code(funcname=name) @@ -1155,7 +1156,7 @@ def generate_julia_diffeq( get_consistent_ics_solver.set_up(self) get_consistent_ics_solver._set_initial_conditions(self, {}, False) ics = pybamm.Vector(self.y0.full()) - ics_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order) + ics_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type) ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name+"_u0") # Change the string to a form for u0 From 25fbf5bfd8f5d2e6f2a93799e7fae10b772f537d Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:27:09 -0400 Subject: [PATCH 057/163] update caches --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 00388adef3..efaaa08e42 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -788,12 +788,12 @@ def create_cache(self,symbol): elif self._cache_type=="dual": if cache_shape[1] == 1: cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+="{} = dualcache(zeros({}),12)".format(cache_shape) + self._cache_and_const_string+="{} = dualcache(zeros{},12)".format(cache_name,cache_shape) self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) elif self._cache_type=="symbolic": if cache_shape[1]==1: cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+=" {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape[0],cache_shape[0]) + self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape,cache_shape[0]) else: raise NotImplementedError("The cache type you've specified has not yet been implemented") return 0 From 3540ad1a0ee4f72c73f7325e7d32479f11f0b96f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:34:51 -0400 Subject: [PATCH 058/163] update caches --- pybamm/expression_tree/operations/evaluate_julia.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index efaaa08e42..7174341b2a 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -788,12 +788,13 @@ def create_cache(self,symbol): elif self._cache_type=="dual": if cache_shape[1] == 1: cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+="{} = dualcache(zeros{},12)".format(cache_name,cache_shape) + self._cache_and_const_string+="{} = dualcache(zeros{},12),".format(cache_name,cache_shape) self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) elif self._cache_type=="symbolic": if cache_shape[1]==1: cache_shape = "({})".format(cache_shape[0]) self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape,cache_shape[0]) + self._cache_initialization_string+=" {} = PyBaMM.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) else: raise NotImplementedError("The cache type you've specified has not yet been implemented") return 0 From d6a5c94af67ff6fc51cf0f075f4cf4446962aa7a Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:41:09 -0400 Subject: [PATCH 059/163] update caches --- pybamm/expression_tree/operations/evaluate_julia.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 7174341b2a..be2dcaa20e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -780,21 +780,24 @@ def create_cache(self,symbol): cache_id = self._cache_id+1 self._cache_id = cache_id cache_name = "cache_{}".format(cache_id) - self._cache_dict[symbol.output] = "cs."+cache_name + if self._cache_type=="standard": if cache_shape[1] == 1: cache_shape = "({})".format(cache_shape[0]) self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) + self._cache_dict[symbol.output] = "cs."+cache_name elif self._cache_type=="dual": if cache_shape[1] == 1: cache_shape = "({})".format(cache_shape[0]) self._cache_and_const_string+="{} = dualcache(zeros{},12),".format(cache_name,cache_shape) self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name elif self._cache_type=="symbolic": if cache_shape[1]==1: cache_shape = "({})".format(cache_shape[0]) self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape,cache_shape[0]) self._cache_initialization_string+=" {} = PyBaMM.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name else: raise NotImplementedError("The cache type you've specified has not yet been implemented") return 0 From 4e35b69ccd6deacb2c0afa570b6d3aacae78aa4f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:47:26 -0400 Subject: [PATCH 060/163] add cache initialization string --- pybamm/expression_tree/operations/evaluate_julia.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index be2dcaa20e..21247656d3 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -864,6 +864,7 @@ def write_function_easy(self,funcname): parameter_string = parameter_string[0:-1] parameter_string += "= p\n" self._function_string = parameter_string + self._function_string + self._function_string = self._cache_initialization_string + self._function_string if my_shape[1] != 1: self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend".format(top_var_name) self._function_string = "function {}(J, y, p, t)\n".format(funcname) + self._function_string From d2aad9772e446f02f68288b736b746b40f25286f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 15 Sep 2022 15:57:07 -0400 Subject: [PATCH 061/163] add cache initialization string --- .../operations/evaluate_julia.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 21247656d3..35a43d902a 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -783,19 +783,25 @@ def create_cache(self,symbol): if self._cache_type=="standard": if cache_shape[1] == 1: - cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape) + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape_st) self._cache_dict[symbol.output] = "cs."+cache_name elif self._cache_type=="dual": if cache_shape[1] == 1: - cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+="{} = dualcache(zeros{},12),".format(cache_name,cache_shape) + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+="{} = dualcache(zeros{},12),\n".format(cache_name,cache_shape_st) self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name elif self._cache_type=="symbolic": if cache_shape[1]==1: - cache_shape = "({})".format(cache_shape[0]) - self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape,cache_shape[0]) + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape_st,cache_shape[0]) self._cache_initialization_string+=" {} = PyBaMM.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name else: From e1819dc63f093adcc8fb995d98cf4ff90231aefa Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 16:56:12 -0400 Subject: [PATCH 062/163] update code writing --- .../operations/evaluate_julia.py | 881 ++++++++---------- 1 file changed, 392 insertions(+), 489 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 35a43d902a..a8a9a7fcc0 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -25,157 +25,6 @@ def is_constant_and_can_evaluate(symbol): else: return False -#BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH -class JuliaBinaryOperation(object): - def __init__(self,left_input,right_input,output,shape): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - -#MatMul and Inner Product are not really the same as the bitwisebinary operations. -class JuliaMatrixMultiplication(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - -class JuliaBitwiseBinaryOperation(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape,operator): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - self.operator = operator - -class JuliaAddition(JuliaBinaryOperation): - pass - -class JuliaSubtraction(JuliaBinaryOperation): - pass - -class JuliaMultiplication(JuliaBinaryOperation): - pass - -class JuliaDivision(JuliaBinaryOperation): - pass - -class JuliaPower(JuliaBinaryOperation): - pass - -#MinMax is special because it does both min and max. Could be folded into JuliaBitwiseBinaryOperation once I do that -class JuliaMinMax(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape,name): - self.left_input = left_input - self.right_input = right_input - self.output = output - self.shape = shape - self.name = name - -#FUNCTIONS -##All Functions Return the same number of arguments they take in, except for minimum and maximum. -class JuliaFunction(object): - pass - -class JuliaBroadcastableFunction(JuliaFunction): - def __init__(self,name,input,output,shape): - self.name = name - self.input = input - self.output = output - self.shape = shape - -class JuliaNegation(JuliaBroadcastableFunction): - pass - -class JuliaMinimumMaximum(JuliaBroadcastableFunction): - pass - - -#Index is a little weird, so it just sits on its own. -class JuliaIndex(object): - def __init__(self,input,output,index): - self.input = input - self.output = output - self.index = index - if type(index) is slice: - if index.step == None: - self.shape = ((index.stop)-(index.start),1) - elif type(index.step) == int: - self.shape = (floor((index.stop-index.start)/index.step),1) - else: - print(index.step) - raise NotImplementedError("asldhfjwaes") - elif type(index) is int: - self.shape = (1,1) - else: - raise NotImplementedError("index must be slice or int") - - - -#Values and Constants -- I will need to change this to inputs, due to t, y, and p. -class JuliaValue(object): - pass - -class JuliaConstant(JuliaValue): - def __init__(self,id,value): - self.id = id - self.value = value - self.shape = value.shape - -class JuliaStateVector(JuliaValue): - def __init__(self,id,loc,shape): - self.id = id - self.loc = loc - self.shape = shape - -class JuliaStateVectorDot(JuliaStateVector): - pass - -class JuliaScalar(JuliaConstant): - def __init__(self,id,value): - self.id = id - self.value = float(value) - self.shape = (1,1) - -class JuliaTime(JuliaScalar): - def __init__(self,id): - self.id = id - self.shape = (1,1) - -class JuliaInput(JuliaScalar): - def __init__(self,id,name): - self.id = id - self.shape = (1,1) - self.name = name - - - -#CONCATENATIONS -class JuliaConcatenation(object): - def __init__(self,output,shape,children): - self.output = output - self.shape = shape - self.children = children - -class JuliaNumpyConcatenation(JuliaConcatenation): - pass - -#NOTE: CURRENTLY THIS BEHAVES EXACTLY LIKE NUMPYCONCATENATION -class JuliaSparseStack(JuliaConcatenation): - pass - - -class JuliaDomainConcatenation(JuliaConcatenation): - def __init__(self,output,shape,children,secondary_dimension_npts,children_slices): - self.output = output - self.shape = shape - self.children = children - self.secondary_dimension_npts = secondary_dimension_npts - self.children_slices = children_slices - - - class JuliaConverter(object): def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit",input_parameter_order=[]): @@ -189,6 +38,7 @@ def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",p self._dae_type = dae_type self._type = "Float64" + self._inline = True #"Caches" #Stores Constants to be Declared in the initial cache #insight: everything is just a line of code @@ -212,72 +62,6 @@ def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",p self._cache_initialization_string = "" #know where to go to find a variable. this could be smoother, there will need to be a ton of boilerplate here. - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaConcatenation): - return self._cache_dict[julia_symbol.output] - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaMinimumMaximum): - return self._cache_dict[julia_symbol.output] - - @multimethod - def get_result_variable_name(self, julia_symbol:JuliaBinaryOperation): - return self._cache_dict[julia_symbol.output] - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaConstant): - return self._const_dict[julia_symbol.id] - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaScalar): - return julia_symbol.value - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaTime): - return "t" - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaInput): - return julia_symbol.name - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaStateVector): - start = julia_symbol.loc[0]+1 - end = julia_symbol.loc[1] - if start==end: - return "y[{}]".format(start) - else: - return "y[{}:{}]".format(start,end) - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaStateVectorDot): - start = julia_symbol.loc[0]+1 - end = julia_symbol.loc[1] - if start==end: - return "dy[{}]".format(start) - else: - return "dy[{}:{}]".format(start,end) - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaBroadcastableFunction): - return self._cache_dict[julia_symbol.output] - - @multimethod - def get_result_variable_name(self,julia_symbol:JuliaIndex): - lower_var = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - index = julia_symbol.index - if type(index) is int: - return "{}[{}]".format(lower_var,index+1) - elif type(index) is slice: - if index.step is None: - return "{}[{}:{}]".format(lower_var,index.start+1,index.stop) - elif type(index.step) is int: - return "{}[{}:{}:{}]".format(lower_var,index.start+1,index.step,index.stop) - else: - raise NotImplementedError("Step has to be an integer.") - else: - raise NotImplementedError("Step must be a slice or an int") - #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. def break_down_binary(self,symbol): #Check for constant @@ -348,7 +132,7 @@ def _convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): def _convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) + self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape,"*") return my_id #Apparently an inner product is a hadamard product in pybamm @@ -356,28 +140,28 @@ def _convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): def _convert_tree_to_intermediate(self,symbol:pybamm.Inner): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape) + self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape,"*") return my_id @multimethod def _convert_tree_to_intermediate(self,symbol:pybamm.Division): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaDivision(id_left,id_right,my_id,my_shape) + self._intermediate[my_id] = JuliaDivision(id_left,id_right,my_id,my_shape,"/") return my_id @multimethod def _convert_tree_to_intermediate(self,symbol: pybamm.Addition): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape) + self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape,"+") return my_id @multimethod def _convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape) + self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape,"-") return my_id @multimethod @@ -398,7 +182,7 @@ def _convert_tree_to_intermediate(self,symbol:pybamm.Maximum): def _convert_tree_to_intermediate(self,symbol:pybamm.Power): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape) + self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape,"^") return my_id @multimethod @@ -576,238 +360,45 @@ def _convert_tree_to_intermediate(self,symbol:pybamm.StateVectorDot): shape = symbol.shape self._intermediate[my_id] = JuliaStateVectorDot(id,points,shape) return my_id - - #utilities for code conversion - def get_variables_for_binary_tree(self,julia_symbol): - left_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.left_input]) - right_input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.right_input]) - result_var_name = self.get_result_variable_name(julia_symbol) - return left_input_var_name,right_input_var_name,result_var_name #convert intermediates to code. Again, all binary trees follow the same pattern so we just define a function to break them down, and then use the MD to find out what code to generate. - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaConcatenation): - input_var_names = [] - num_cols = julia_symbol.shape[1] - my_name = self.get_result_variable_name(julia_symbol) - #assume we don't have tensors. Already asserted that concatenations have to have the same width. - if num_cols==1: - right_parenthesis = "]" - vec=True - else: - right_parenthesis = ",:]" - vec=False - #do the 0th one outside of the loop to initialize - child = julia_symbol.children[0] - child_var = self._intermediate[child] - child_var_name = self.get_result_variable_name(self._intermediate[child]) - start_row = 1 - if child_var.shape[0] == 0: - end_row = 1 - code = "" - elif child_var.shape[0] == 1: - end_row = 1 - if vec: - code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) - else: - code = "{}[{}{} = (@view {})\n".format(my_name,start_row,right_parenthesis,child_var_name) - else: - start_row = 1 - end_row = child_var.shape[0] - code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + #Cache and Const Creation + def create_cache(self,symbol): + my_id = symbol.output + + cache_shape = self._intermediate[my_id].shape + cache_id = self._cache_id+1 + self._cache_id = cache_id + cache_name = "cache_{}".format(cache_id) - for child in julia_symbol.children[1:]: - child_var = self._intermediate[child] - child_var_name = self.get_result_variable_name(self._intermediate[child]) - if child_var.shape[0] == 0: - continue - elif child_var.shape[0] == 1: - start_row = end_row+1 - end_row = start_row+1 - if vec: - code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) - else: - code += "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + if self._cache_type=="standard": + if cache_shape[1] == 1: + cache_shape_st = "({})".format(cache_shape[0]) else: - start_row = end_row+1 - end_row = start_row+child_var.shape[0]-1 - code += "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) - - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaDomainConcatenation): - input_var_names = [] - num_cols = julia_symbol.shape[1] - my_name = self.get_result_variable_name(julia_symbol) - - #assume we don't have tensors. Already asserted that concatenations have to have the same width. - if num_cols==1: - right_parenthesis = "]" - vec=True - else: - right_parenthesis = ",:]" - vec=False - #do the 0th one outside of the loop to initialize - end_row = 0 - code = "" - for i in range(julia_symbol.secondary_dimension_npts): - for c in range(len(julia_symbol.children)): - child_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.children[c]]) - this_slice = list(julia_symbol.children_slices[c].values())[0][i] - start = this_slice.start - stop = this_slice.stop - start_row = end_row+1 - end_row = start_row+(stop-start)-1 - code += "{}[{}:{}{} .= (@view {}[{}:{}{})\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) - - self._function_string+=code - return 0 - - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaMatrixMultiplication): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaAddition): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} .+ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .+ {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaSubtraction): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} .- {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .- {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaMultiplication): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} .* {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} .* {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaDivision): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {} ./ {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {} ./ {}".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaBroadcastableFunction): - result_var_name = self.get_result_variable_name(julia_symbol) - input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - code = "{} .= {}.({})\n".format(result_var_name,julia_symbol.name,input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaNegation): - result_var_name = self.get_result_variable_name(julia_symbol) - input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - code = "{} .= -{}\n".format(result_var_name,input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaMinimumMaximum): - result_var_name = self.get_result_variable_name(julia_symbol) - input_var_name = self.get_result_variable_name(self._intermediate[julia_symbol.input]) - code = "{} .= {}({})\n".format(result_var_name,julia_symbol.name,input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaMinMax): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) - else: - code = "{} = {}({},{})\n".format(result_var_name,julia_symbol.name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaPower): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {}.^{}\n".format(result_var_name,left_input_var_name,right_input_var_name) - else: - code = "{} = {}.^{})\n".format(result_var_name,left_input_var_name,right_input_var_name) - self._function_string+=code - return 0 - - @multimethod - def convert_intermediate_to_code(self,julia_symbol:JuliaBitwiseBinaryOperation): - left_input_var_name,right_input_var_name,result_var_name = self.get_variables_for_binary_tree(julia_symbol) - if self._preallocate: - code = "{} .= {}.{}{}\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) - else: - code = "{} = {}.{}{})\n".format(result_var_name,left_input_var_name,julia_symbol.operator,right_input_var_name) - self._function_string+=code - return 0 - - #Cache and Const Creation - @multimethod - def create_cache(self,symbol): - my_id = symbol.output - - cache_shape = self._intermediate[my_id].shape - cache_id = self._cache_id+1 - self._cache_id = cache_id - cache_name = "cache_{}".format(cache_id) - - if self._cache_type=="standard": - if cache_shape[1] == 1: - cache_shape_st = "({})".format(cache_shape[0]) - else: - cache_shape_st = cache_shape - self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape_st) - self._cache_dict[symbol.output] = "cs."+cache_name - elif self._cache_type=="dual": - if cache_shape[1] == 1: - cache_shape_st = "({})".format(cache_shape[0]) - else: - cache_shape_st = cache_shape - self._cache_and_const_string+="{} = dualcache(zeros{},12),\n".format(cache_name,cache_shape_st) - self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) - self._cache_dict[symbol.output] = cache_name - elif self._cache_type=="symbolic": - if cache_shape[1]==1: - cache_shape_st = "({})".format(cache_shape[0]) - else: - cache_shape_st = cache_shape - self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape_st,cache_shape[0]) - self._cache_initialization_string+=" {} = PyBaMM.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) - self._cache_dict[symbol.output] = cache_name - else: - raise NotImplementedError("The cache type you've specified has not yet been implemented") - return 0 - + cache_shape_st = cache_shape + self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape_st) + self._cache_dict[symbol.output] = "cs."+cache_name + elif self._cache_type=="dual": + if cache_shape[1] == 1: + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+="{} = dualcache(zeros{},12),\n".format(cache_name,cache_shape_st) + self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name + elif self._cache_type=="symbolic": + if cache_shape[1]==1: + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape_st,cache_shape[0]) + self._cache_initialization_string+=" {} = PyBaMM.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name + else: + raise NotImplementedError("The cache type you've specified has not yet been implemented") + return cache_name + def create_const(self,symbol): @@ -854,14 +445,14 @@ def clear(self): self._const_id = 0 #Just get something working here, so can start actual testing - def write_function_easy(self,funcname): + def write_function_easy(self,funcname,inline=True): #start with the closure self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string self._cache_and_const_string += ")\n" top = self._intermediate[next(reversed(self._intermediate))] - top_var_name = self.get_result_variable_name(top) + top_var_name = top._convert_intermediate_to_code(self,inline=inline) my_shape = top.shape if len(self.input_parameter_order) != 0: parameter_string = "" @@ -880,9 +471,6 @@ def write_function_easy(self,funcname): elif self._dae_type=="implicit": self._function_string+="out[:] .= {}\nreturn nothing\nend\nend".format(top_var_name) self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname) + self._function_string - - - return 0 @@ -904,39 +492,354 @@ def convert_tree_to_intermediate(self,symbol,len_rhs=None): return 0 #rework this at some point - def build_julia_code(self,funcname="f"): - for entry in self._intermediate.values(): - if issubclass(type(entry),JuliaBinaryOperation): - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - elif type(entry) is JuliaConstant: - self.create_const(entry) - elif type(entry) is JuliaIndex: - continue - elif type(entry) is JuliaStateVector: - continue - elif type(entry) is JuliaStateVectorDot: - continue - elif type(entry) is JuliaScalar: - continue - elif type(entry) is JuliaBroadcastableFunction: - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - elif type(entry) is JuliaNegation: - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - elif type(entry) is JuliaMinimumMaximum: - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - elif type(entry) is JuliaTime: - continue - elif type(entry) is JuliaInput: - continue - elif issubclass(type(entry),JuliaConcatenation): - self.create_cache(entry) - self.convert_intermediate_to_code(entry) - else: - raise NotImplementedError("uh oh") - self.write_function_easy(funcname) + def build_julia_code(self,funcname="f",inline=True): + #get top node of tree + self.write_function_easy(funcname,inline=inline) string = self._cache_and_const_string+self._function_string return string + + +#BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH +class JuliaBinaryOperation(object): + def __init__(self,left_input,right_input,output,shape): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + def get_binary_inputs(self,converter:JuliaConverter,inline=True): + left_input_var_name = converter._intermediate[self.left_input]._convert_intermediate_to_code(converter,inline=inline) + right_input_var_name = converter._intermediate[self.right_input]._convert_intermediate_to_code(converter,inline=inline) + return left_input_var_name,right_input_var_name + +#MatMul and Inner Product are not really the same as the bitwisebinary operations. +class JuliaMatrixMultiplication(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=False): + result_var_name = converter.create_cache(self) + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=inline) + result_var_name = converter._cache_dict[self.output] + if converter._preallocate: + code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) + else: + code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) + converter._function_string+=code + return result_var_name + + +#Includes Addition, subtraction, multiplication, division, power, minimum, and maximum +class JuliaBitwiseBinaryOperation(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape,operator): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + self.operator = operator + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + inline = inline & converter._inline + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=inline) + + if not inline: + result_var_name = converter.create_cache(self) + if converter._preallocate: + code = "{} .= {}.{}{}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) + else: + code = "{} = {}.{}{})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) + converter._function_string+=code + elif inline: + result_var_name = "({} .{} {})".format(left_input_var_name,self.operator,right_input_var_name) + return result_var_name + +class JuliaAddition(JuliaBitwiseBinaryOperation): + pass + +class JuliaSubtraction(JuliaBitwiseBinaryOperation): + pass + +class JuliaMultiplication(JuliaBitwiseBinaryOperation): + pass + +class JuliaDivision(JuliaBitwiseBinaryOperation): + pass + +class JuliaPower(JuliaBitwiseBinaryOperation): + pass + +#MinMax is special because it does both min and max. Could be folded into JuliaBitwiseBinaryOperation once I do that +class JuliaMinMax(JuliaBinaryOperation): + def __init__(self,left_input,right_input,output,shape,name): + self.left_input = left_input + self.right_input = right_input + self.output = output + self.shape = shape + self.name = name + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + inline = inline & converter._inline + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=inline) + + if not inline: + result_var_name = converter.create_cache(self) + if converter._preallocate: + code = "{} .= {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) + else: + code = "{} = {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) + converter._function_string+=code + elif inline: + result_var_name = "{}({},{})".format(self.name,left_input_var_name,right_input_var_name) + return result_var_name + +#FUNCTIONS +##All Functions Return the same number of arguments they take in, except for minimum and maximum. +class JuliaFunction(object): + pass + +class JuliaBroadcastableFunction(JuliaFunction): + def __init__(self,name,input,output,shape): + self.name = name + self.input = input + self.output = output + self.shape = shape + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + inline = inline & converter._inline + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + if not inline: + result_var_name = converter.create_cache(self) + if converter._preallocate: + code = "{} .= {}.({})\n".format(result_var_name,self.name,input_var_name) + else: + code = "{} = {}.({})\n".format(result_var_name,self.name,input_var_name) + converter._function_string+=code + else: + result_var_name = "({}.({}))".format(self.name,input_var_name) + return result_var_name + +class JuliaNegation(JuliaBroadcastableFunction): + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + inline = inline & converter._inline + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + if not inline: + result_var_name = converter.create_cache(self) + if converter._preallocate: + code = "{} .= -{}\n".format(result_var_name,input_var_name) + else: + code = "{} = -{}\n".format(result_var_name,input_var_name) + converter._function_string+=code + else: + result_var_name = "(-{})".format(input_var_name) + return result_var_name + +class JuliaMinimumMaximum(JuliaBroadcastableFunction): + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + inline = inline & converter._inline + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + if not inline: + result_var_name = converter.create_cache(self) + if converter._preallocate: + code = "{} .= {}({})\n".format(result_var_name,self.name,input_var_name) + else: + code = "{} = {}({})\n".format(result_var_name,self.name,input_var_name) + converter._function_string+=code + else: + result_var_name = "{}({})".format(self.name,input_var_name) + return result_var_name + pass + + +#Index is a little weird, so it just sits on its own. +class JuliaIndex(object): + def __init__(self,input,output,index): + self.input = input + self.output = output + self.index = index + if type(index) is slice: + if index.step == None: + self.shape = ((index.stop)-(index.start),1) + elif type(index.step) == int: + self.shape = (floor((index.stop-index.start)/index.step),1) + else: + print(index.step) + raise NotImplementedError("asldhfjwaes") + elif type(index) is int: + self.shape = (1,1) + else: + raise NotImplementedError("index must be slice or int") + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + index = self.index + if type(index) is int: + return "{}[{}]".format(input_var_name,index+1) + elif type(index) is slice: + if index.step is None: + return "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) + elif type(index.step) is int: + return "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) + else: + raise NotImplementedError("Step has to be an integer.") + else: + raise NotImplementedError("Step must be a slice or an int") + + + +#Values and Constants -- I will need to change this to inputs, due to t, y, and p. +class JuliaValue(object): + pass + +class JuliaConstant(JuliaValue): + def __init__(self,id,value): + self.id = id + self.value = value + self.shape = value.shape + def _convert_intermediate_to_code(self,converter,inline=True): + converter.create_const(self) + return converter._const_dict[self.id] + +class JuliaStateVector(JuliaValue): + def __init__(self,id,loc,shape): + self.id = id + self.loc = loc + self.shape = shape + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + start = self.loc[0]+1 + end = self.loc[1] + if start==end: + return "(@view y[{}])".format(start) + else: + return "(@view y[{}:{}])".format(start,end) + +class JuliaStateVectorDot(JuliaStateVector): + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + start = self.loc[0]+1 + end = self.loc[1] + if start==end: + return "(@view dy[{}])".format(start) + else: + return "(@view dy[{}:{}])".format(start,end) + +class JuliaScalar(JuliaConstant): + def __init__(self,id,value): + self.id = id + self.value = float(value) + self.shape = (1,1) + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + return self.value + +class JuliaTime(JuliaScalar): + def __init__(self,id): + self.id = id + self.shape = (1,1) + def _convert_intermediate_to_code(self,converter,inline=True): + return "t" + +class JuliaInput(JuliaScalar): + def __init__(self,id,name): + self.id = id + self.shape = (1,1) + self.name = name + def _convert_intermediate_to_code(self,converter,inline=True): + return self.name + + + +#CONCATENATIONS +class JuliaConcatenation(object): + def __init__(self,output,shape,children): + self.output = output + self.shape = shape + self.children = children + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + input_var_names = [] + num_cols = self.shape[1] + my_name = converter.create_cache(self) + + #assume we don't have tensors. Already asserted that concatenations have to have the same width. + if num_cols==1: + right_parenthesis = "]" + vec=True + else: + right_parenthesis = ",:]" + vec=False + + #do the 0th one outside of the loop to initialize + child = self.children[0] + child_var = converter._intermediate[child] + child_var_name = child_var._convert_intermediate_to_code(converter,inline=False) + start_row = 1 + if child_var.shape[0] == 0: + end_row = 1 + code = "" + elif child_var.shape[0] == 1: + end_row = 1 + if vec: + code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) + else: + code = "{}[{}{} = (@view {})\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + start_row = 1 + end_row = child_var.shape[0] + code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + + for child in self.children[1:]: + child_var = converter._intermediate[child] + child_var_name = child_var._convert_intermediate_to_code(converter,inline=False) + if child_var.shape[0] == 0: + continue + elif child_var.shape[0] == 1: + start_row = end_row+1 + end_row = start_row+1 + if vec: + code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + code += "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + start_row = end_row+1 + end_row = start_row+child_var.shape[0]-1 + code += "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + + converter._function_string+=code + return my_name + +class JuliaNumpyConcatenation(JuliaConcatenation): + pass + +#NOTE: CURRENTLY THIS BEHAVES EXACTLY LIKE NUMPYCONCATENATION +class JuliaSparseStack(JuliaConcatenation): + pass + +class JuliaDomainConcatenation(JuliaConcatenation): + def __init__(self,output,shape,children,secondary_dimension_npts,children_slices): + self.output = output + self.shape = shape + self.children = children + self.secondary_dimension_npts = secondary_dimension_npts + self.children_slices = children_slices + def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + input_var_names = [] + num_cols = self.shape[1] + result_var_name = converter.create_cache(self) + + #assume we don't have tensors. Already asserted that concatenations have to have the same width. + if num_cols==1: + right_parenthesis = "]" + vec=True + else: + right_parenthesis = ",:]" + vec=False + #do the 0th one outside of the loop to initialize + end_row = 0 + code = "" + for i in range(self.secondary_dimension_npts): + for c in range(len(self.children)): + child = converter._intermediate[self.children[c]] + child_var_name = child._convert_intermediate_to_code(converter,inline=False) + this_slice = list(self.children_slices[c].values())[0][i] + start = this_slice.start + stop = this_slice.stop + start_row = end_row+1 + end_row = start_row+(stop-start)-1 + code += "{}[{}:{}{} .= (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + + converter._function_string+=code + return result_var_name From de9926cfb156dc3851e65744fff30fc87ad6561f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 17:18:47 -0400 Subject: [PATCH 063/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index a8a9a7fcc0..de0c03f5e3 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -397,7 +397,7 @@ def create_cache(self,symbol): self._cache_dict[symbol.output] = cache_name else: raise NotImplementedError("The cache type you've specified has not yet been implemented") - return cache_name + return self._cache_dict[my_id] @@ -447,12 +447,10 @@ def clear(self): #Just get something working here, so can start actual testing def write_function_easy(self,funcname,inline=True): #start with the closure - self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string - self._cache_and_const_string += ")\n" - - top = self._intermediate[next(reversed(self._intermediate))] top_var_name = top._convert_intermediate_to_code(self,inline=inline) + self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string + self._cache_and_const_string += ")\n" my_shape = top.shape if len(self.input_parameter_order) != 0: parameter_string = "" From 8e3ae3f46790a28be73008ccfcce88027904aec2 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 19:18:04 -0400 Subject: [PATCH 064/163] fix bugs --- .../operations/evaluate_julia.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index de0c03f5e3..fbca9977cb 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -168,14 +168,14 @@ def _convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): def _convert_tree_to_intermediate(self,symbol:pybamm.Minimum): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min.") + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min") return my_id @multimethod def _convert_tree_to_intermediate(self,symbol:pybamm.Maximum): id_left,id_right,my_id = self.break_down_binary(symbol) my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max.") + self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max") return my_id @multimethod @@ -543,12 +543,12 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: - code = "{} .= {}.{}{}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) + code = "@. {} = {}{}{}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) else: code = "{} = {}.{}{})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) converter._function_string+=code elif inline: - result_var_name = "({} .{} {})".format(left_input_var_name,self.operator,right_input_var_name) + result_var_name = "({} {} {})".format(left_input_var_name,self.operator,right_input_var_name) return result_var_name class JuliaAddition(JuliaBitwiseBinaryOperation): @@ -581,7 +581,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: - code = "{} .= {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) + code = "@. {} = {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) else: code = "{} = {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) converter._function_string+=code @@ -606,12 +606,13 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: - code = "{} .= {}.({})\n".format(result_var_name,self.name,input_var_name) + code = "@. {} = {}({})\n".format(result_var_name,self.name,input_var_name) else: code = "{} = {}.({})\n".format(result_var_name,self.name,input_var_name) converter._function_string+=code else: - result_var_name = "({}.({}))".format(self.name,input_var_name) + #assume an @. has already been issued + result_var_name = "({}({}))".format(self.name,input_var_name) return result_var_name class JuliaNegation(JuliaBroadcastableFunction): @@ -621,7 +622,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: - code = "{} .= -{}\n".format(result_var_name,input_var_name) + code = "@. {} = -{}\n".format(result_var_name,input_var_name) else: code = "{} = -{}\n".format(result_var_name,input_var_name) converter._function_string+=code @@ -636,7 +637,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: - code = "{} .= {}({})\n".format(result_var_name,self.name,input_var_name) + code = "@. {} = {}({})\n".format(result_var_name,self.name,input_var_name) else: code = "{} = {}({})\n".format(result_var_name,self.name,input_var_name) converter._function_string+=code @@ -771,13 +772,13 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): elif child_var.shape[0] == 1: end_row = 1 if vec: - code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) + code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) else: code = "{}[{}{} = (@view {})\n".format(my_name,start_row,right_parenthesis,child_var_name) else: start_row = 1 end_row = child_var.shape[0] - code = "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) for child in self.children[1:]: child_var = converter._intermediate[child] @@ -790,11 +791,11 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if vec: code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: - code += "{}[{}{} .= {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code += "@. {}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: start_row = end_row+1 end_row = start_row+child_var.shape[0]-1 - code += "{}[{}:{}{} .= {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + code += "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) converter._function_string+=code return my_name @@ -837,7 +838,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): stop = this_slice.stop start_row = end_row+1 end_row = start_row+(stop-start)-1 - code += "{}[{}:{}{} .= (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + code += "@. {}[{}:{}{} = (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) converter._function_string+=code return result_var_name From 716d98c87afdd9e55503d16c2f7b6a271b7b22ff Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 20:06:37 -0400 Subject: [PATCH 065/163] fix bugs --- .../operations/evaluate_julia.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index fbca9977cb..d93b5ec4e1 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -448,7 +448,7 @@ def clear(self): def write_function_easy(self,funcname,inline=True): #start with the closure top = self._intermediate[next(reversed(self._intermediate))] - top_var_name = top._convert_intermediate_to_code(self,inline=inline) + top_var_name = top._convert_intermediate_to_code(self,inline=False) self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string self._cache_and_const_string += ")\n" my_shape = top.shape @@ -632,19 +632,14 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): class JuliaMinimumMaximum(JuliaBroadcastableFunction): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): - inline = inline & converter._inline - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) - if not inline: - result_var_name = converter.create_cache(self) - if converter._preallocate: - code = "@. {} = {}({})\n".format(result_var_name,self.name,input_var_name) - else: - code = "{} = {}({})\n".format(result_var_name,self.name,input_var_name) - converter._function_string+=code + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + result_var_name = converter.create_cache(self) + if converter._preallocate: + code = "{} .= {}({})\n".format(result_var_name,self.name,input_var_name) else: - result_var_name = "{}({})".format(self.name,input_var_name) + code = "{} = {}({})\n".format(result_var_name,self.name,input_var_name) + converter._function_string+=code return result_var_name - pass #Index is a little weird, so it just sits on its own. @@ -704,7 +699,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): start = self.loc[0]+1 end = self.loc[1] if start==end: - return "(@view y[{}])".format(start) + return "(y[{}])".format(start) else: return "(@view y[{}:{}])".format(start,end) @@ -713,7 +708,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): start = self.loc[0]+1 end = self.loc[1] if start==end: - return "(@view dy[{}])".format(start) + return "(dy[{}])".format(start) else: return "(@view dy[{}:{}])".format(start,end) From 3f7b3dc4a1f1bd5a29ae325ea2e5f4852f9d8cd4 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 20:10:35 -0400 Subject: [PATCH 066/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index d93b5ec4e1..2a94df250d 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -622,7 +622,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: - code = "@. {} = -{}\n".format(result_var_name,input_var_name) + code = "@. {} = - {}\n".format(result_var_name,input_var_name) else: code = "{} = -{}\n".format(result_var_name,input_var_name) converter._function_string+=code From b9b23fb825a0643c077dc3ee67ff5991f17dfdd6 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 20:16:34 -0400 Subject: [PATCH 067/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 2a94df250d..141c53fc7e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -543,9 +543,9 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: - code = "@. {} = {}{}{}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) + code = "@. {} = {} {} {}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) else: - code = "{} = {}.{}{})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) + code = "{} = {} .{} {})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) converter._function_string+=code elif inline: result_var_name = "({} {} {})".format(left_input_var_name,self.operator,right_input_var_name) @@ -627,7 +627,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): code = "{} = -{}\n".format(result_var_name,input_var_name) converter._function_string+=code else: - result_var_name = "(-{})".format(input_var_name) + result_var_name = "(- {})".format(input_var_name) return result_var_name class JuliaMinimumMaximum(JuliaBroadcastableFunction): From 0fe5686807cc47f2b873c8629791f3251a7a0de0 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 21:15:06 -0400 Subject: [PATCH 068/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 141c53fc7e..82762dcf23 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -518,7 +518,7 @@ def __init__(self,left_input,right_input,output,shape): self.shape = shape def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=False): result_var_name = converter.create_cache(self) - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=inline) + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=False) result_var_name = converter._cache_dict[self.output] if converter._preallocate: code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) @@ -583,7 +583,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter._preallocate: code = "@. {} = {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) else: - code = "{} = {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) + code = "{} = {}.({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) converter._function_string+=code elif inline: result_var_name = "{}({},{})".format(self.name,left_input_var_name,right_input_var_name) From 60fbd94ef7bf98a98bb1fd6b753e20179d75db83 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 21:58:55 -0400 Subject: [PATCH 069/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 82762dcf23..e7a4ff3033 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -759,7 +759,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): #do the 0th one outside of the loop to initialize child = self.children[0] child_var = converter._intermediate[child] - child_var_name = child_var._convert_intermediate_to_code(converter,inline=False) + child_var_name = child_var._convert_intermediate_to_code(converter,inline=True) start_row = 1 if child_var.shape[0] == 0: end_row = 1 @@ -769,7 +769,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if vec: code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) else: - code = "{}[{}{} = (@view {})\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "@. {}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: start_row = 1 end_row = child_var.shape[0] From d9a8dbd104e38021943344e3ec1955bbf9cfee36 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 22:02:03 -0400 Subject: [PATCH 070/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index e7a4ff3033..38e0e38649 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -769,7 +769,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if vec: code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) else: - code = "@. {}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: start_row = 1 end_row = child_var.shape[0] From c5076100435046e685beacee84c4fa1f21a59e1f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 23:08:00 -0400 Subject: [PATCH 071/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 38e0e38649..6e17497390 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -538,8 +538,7 @@ def __init__(self,left_input,right_input,output,shape,operator): self.operator = operator def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): inline = inline & converter._inline - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=inline) - + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) if not inline: result_var_name = converter.create_cache(self) if converter._preallocate: @@ -576,7 +575,7 @@ def __init__(self,left_input,right_input,output,shape,name): self.name = name def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): inline = inline & converter._inline - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=inline) + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) if not inline: result_var_name = converter.create_cache(self) @@ -777,7 +776,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): for child in self.children[1:]: child_var = converter._intermediate[child] - child_var_name = child_var._convert_intermediate_to_code(converter,inline=False) + child_var_name = child_var._convert_intermediate_to_code(converter,inline=True) if child_var.shape[0] == 0: continue elif child_var.shape[0] == 1: @@ -827,7 +826,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): for i in range(self.secondary_dimension_npts): for c in range(len(self.children)): child = converter._intermediate[self.children[c]] - child_var_name = child._convert_intermediate_to_code(converter,inline=False) + child_var_name = child._convert_intermediate_to_code(converter,inline=True) this_slice = list(self.children_slices[c].values())[0][i] start = this_slice.start stop = this_slice.stop From d5adf9c72ca9263bfb32085833deba9ea17edb3a Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Sun, 18 Sep 2022 23:19:29 -0400 Subject: [PATCH 072/163] fix bugs --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 6e17497390..2ecdcb20f2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -660,7 +660,7 @@ def __init__(self,input,output,index): else: raise NotImplementedError("index must be slice or int") def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) index = self.index if type(index) is int: return "{}[{}]".format(input_var_name,index+1) From 5b298a0c1b68c115c2fdcfe90cb770f2b2d0a33c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 12:04:14 -0400 Subject: [PATCH 073/163] check for repeat code --- .../operations/evaluate_julia.py | 22 +++++++++++++++++++ pybamm/models/base_model.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 2ecdcb20f2..7a1b56b620 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -61,6 +61,10 @@ def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",p self._return_string = "" self._cache_initialization_string = "" + def cache_exists(self,id): + return (self._cache_dict.get(id)!=None) + + #know where to go to find a variable. this could be smoother, there will need to be a ton of boilerplate here. #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. def break_down_binary(self,symbol): @@ -517,6 +521,8 @@ def __init__(self,left_input,right_input,output,shape): self.output = output self.shape = shape def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=False): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] result_var_name = converter.create_cache(self) left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=False) result_var_name = converter._cache_dict[self.output] @@ -537,6 +543,8 @@ def __init__(self,left_input,right_input,output,shape,operator): self.shape = shape self.operator = operator def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] inline = inline & converter._inline left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) if not inline: @@ -574,6 +582,8 @@ def __init__(self,left_input,right_input,output,shape,name): self.shape = shape self.name = name def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] inline = inline & converter._inline left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) @@ -600,6 +610,8 @@ def __init__(self,name,input,output,shape): self.output = output self.shape = shape def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] inline = inline & converter._inline input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) if not inline: @@ -616,6 +628,8 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): class JuliaNegation(JuliaBroadcastableFunction): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] inline = inline & converter._inline input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) if not inline: @@ -631,6 +645,8 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): class JuliaMinimumMaximum(JuliaBroadcastableFunction): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) result_var_name = converter.create_cache(self) if converter._preallocate: @@ -660,6 +676,8 @@ def __init__(self,input,output,index): else: raise NotImplementedError("index must be slice or int") def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) index = self.index if type(index) is int: @@ -743,6 +761,8 @@ def __init__(self,output,shape,children): self.shape = shape self.children = children def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] input_var_names = [] num_cols = self.shape[1] my_name = converter.create_cache(self) @@ -809,6 +829,8 @@ def __init__(self,output,shape,children,secondary_dimension_npts,children_slices self.secondary_dimension_npts = secondary_dimension_npts self.children_slices = children_slices def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + if converter.cache_exists(self.output): + return converter._cache_dict[self.output] input_var_names = [] num_cols = self.shape[1] result_var_name = converter.create_cache(self) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index d5c90fc0ba..1d599ea638 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1167,7 +1167,7 @@ def generate_julia_diffeq( size_state = self.concatenated_initial_conditions.size state_vector = pybamm.StateVector(slice(0,(size_state-1))) expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) - jac_converter = pybamm.JuliaConverter() + jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order) jac_converter.convert_tree_to_intermediate(expr) jac_str = jac_converter.build_julia_code(funcname="jac_"+name) return eqn_str,ics_str,jac_str From 0112d80f8a7c6c22f3882d1224209e736f6bf0ac Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 12:33:17 -0400 Subject: [PATCH 074/163] fix jacobian size --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 1d599ea638..c2499b429f 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1165,7 +1165,7 @@ def generate_julia_diffeq( if generate_jacobian: size_state = self.concatenated_initial_conditions.size - state_vector = pybamm.StateVector(slice(0,(size_state-1))) + state_vector = pybamm.StateVector(slice(0,size_state)) expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order) jac_converter.convert_tree_to_intermediate(expr) From c589006abbb1b9637662343c9c8e18bfe45ce4af Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 12:46:15 -0400 Subject: [PATCH 075/163] allocations --- .../operations/evaluate_julia.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 7a1b56b620..ab700238bf 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -678,7 +678,7 @@ def __init__(self,input,output,index): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) index = self.index if type(index) is int: return "{}[{}]".format(input_var_name,index+1) @@ -845,16 +845,27 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): #do the 0th one outside of the loop to initialize end_row = 0 code = "" - for i in range(self.secondary_dimension_npts): + if self.secondary_dimension_npts == 1: for c in range(len(self.children)): child = converter._intermediate[self.children[c]] child_var_name = child._convert_intermediate_to_code(converter,inline=True) - this_slice = list(self.children_slices[c].values())[0][i] + this_slice = list(self.children_slices[c].values())[0][0] start = this_slice.start stop = this_slice.stop start_row = end_row+1 end_row = start_row+(stop-start)-1 - code += "@. {}[{}:{}{} = (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + code += "@. {}[{}:{}{} = {}\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name) + else: + for i in range(self.secondary_dimension_npts): + for c in range(len(self.children)): + child = converter._intermediate[self.children[c]] + child_var_name = child._convert_intermediate_to_code(converter,inline=True) + this_slice = list(self.children_slices[c].values())[0][i] + start = this_slice.start + stop = this_slice.stop + start_row = end_row+1 + end_row = start_row+(stop-start)-1 + code += "@. {}[{}:{}{} = (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) converter._function_string+=code return result_var_name From 221e4e19c585edc88f310011e14b67a26c666c65 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 15:02:41 -0400 Subject: [PATCH 076/163] let --- pybamm/expression_tree/operations/evaluate_julia.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index ab700238bf..fd2d8407b9 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -453,7 +453,7 @@ def write_function_easy(self,funcname,inline=True): #start with the closure top = self._intermediate[next(reversed(self._intermediate))] top_var_name = top._convert_intermediate_to_code(self,inline=False) - self._cache_and_const_string = "begin\ncs = (\n" + self._cache_and_const_string + self._cache_and_const_string = "begin\n{} = let cs = (\n".format(funcname) + self._cache_and_const_string self._cache_and_const_string += ")\n" my_shape = top.shape if len(self.input_parameter_order) != 0: @@ -466,13 +466,13 @@ def write_function_easy(self,funcname,inline=True): self._function_string = self._cache_initialization_string + self._function_string if my_shape[1] != 1: self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend".format(top_var_name) - self._function_string = "function {}(J, y, p, t)\n".format(funcname) + self._function_string + self._function_string = "function {}(J, y, p, t)\n".format(funcname+"with_consts") + self._function_string elif self._dae_type=="semi-explicit": self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend".format(top_var_name) - self._function_string = "function {}(dy, y, p, t)\n".format(funcname) + self._function_string + self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string elif self._dae_type=="implicit": self._function_string+="out[:] .= {}\nreturn nothing\nend\nend".format(top_var_name) - self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname) + self._function_string + self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string return 0 From 42a7fae10eb6d857b9ef87f1f6a45f69220aa55c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 15:03:03 -0400 Subject: [PATCH 077/163] let --- pybamm/expression_tree/operations/evaluate_julia.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index fd2d8407b9..84719c7647 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -465,13 +465,13 @@ def write_function_easy(self,funcname,inline=True): self._function_string = parameter_string + self._function_string self._function_string = self._cache_initialization_string + self._function_string if my_shape[1] != 1: - self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend".format(top_var_name) + self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(J, y, p, t)\n".format(funcname+"with_consts") + self._function_string elif self._dae_type=="semi-explicit": - self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend".format(top_var_name) + self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string elif self._dae_type=="implicit": - self._function_string+="out[:] .= {}\nreturn nothing\nend\nend".format(top_var_name) + self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string return 0 From bcd4b0593276a1f267483375e24f0f89bb76d446 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 15:20:50 -0400 Subject: [PATCH 078/163] let --- pybamm/expression_tree/operations/evaluate_julia.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 84719c7647..987a36f32b 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -372,8 +372,8 @@ def create_cache(self,symbol): my_id = symbol.output cache_shape = self._intermediate[my_id].shape - cache_id = self._cache_id+1 - self._cache_id = cache_id + cache_id = self._cache_id + self._cache_id +=1 cache_name = "cache_{}".format(cache_id) if self._cache_type=="standard": @@ -463,6 +463,8 @@ def write_function_easy(self,funcname,inline=True): parameter_string = parameter_string[0:-1] parameter_string += "= p\n" self._function_string = parameter_string + self._function_string + self._function_string.replace("cache_0","dy") + self._function_string.replace("cs.dy","dy") self._function_string = self._cache_initialization_string + self._function_string if my_shape[1] != 1: self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) From 204576cba12b8afbeae62d5084071b8ca442292a Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 16:19:15 -0400 Subject: [PATCH 079/163] smooth end of function --- .../operations/evaluate_julia.py | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 987a36f32b..4b3eb9239c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -463,18 +463,19 @@ def write_function_easy(self,funcname,inline=True): parameter_string = parameter_string[0:-1] parameter_string += "= p\n" self._function_string = parameter_string + self._function_string - self._function_string.replace("cache_0","dy") - self._function_string.replace("cs.dy","dy") self._function_string = self._cache_initialization_string + self._function_string if my_shape[1] != 1: - self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) - self._function_string = "function {}(J, y, p, t)\n".format(funcname+"with_consts") + self._function_string + self._function_string = self._function_string.replace(top_var_name,"J") + self._function_string += "\nreturn nothing\nend\nend\nend" + self._function_string = "function {}(J, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="semi-explicit": - self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) - self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string + self._function_string = self._function_string.replace(top_var_name,"dy") + self._function_string += "\nreturn nothing\nend\nend\nend" + self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="implicit": - self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) - self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string + self._function_string = self._function_string.replace(top_var_name,"out") + self._function_string += "\nreturn nothing\nend\nend\nend" + self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string return 0 @@ -680,19 +681,30 @@ def __init__(self,input,output,index): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] + inline = converter._inline & inline input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) index = self.index if type(index) is int: - return "{}[{}]".format(input_var_name,index+1) + rhs= "{}[{}]".format(input_var_name,index+1) elif type(index) is slice: if index.step is None: - return "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) + rhs= "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) elif type(index.step) is int: - return "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) + rhs= "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) else: raise NotImplementedError("Step has to be an integer.") else: raise NotImplementedError("Step must be a slice or an int") + + if inline: + return rhs + else: + result_var_name = converter.create_cache(self) + code = "@. {} = {}\n".format(result_var_name,rhs) + converter._function_string+=code + return result_var_name + + From 315bed5ab2aa574a4e44c29512daa6eaf77d8061 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 16:39:21 -0400 Subject: [PATCH 080/163] revert --- .../operations/evaluate_julia.py | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 4b3eb9239c..8f4ad870c2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -372,8 +372,8 @@ def create_cache(self,symbol): my_id = symbol.output cache_shape = self._intermediate[my_id].shape - cache_id = self._cache_id - self._cache_id +=1 + cache_id = self._cache_id+1 + self._cache_id = cache_id cache_name = "cache_{}".format(cache_id) if self._cache_type=="standard": @@ -465,17 +465,14 @@ def write_function_easy(self,funcname,inline=True): self._function_string = parameter_string + self._function_string self._function_string = self._cache_initialization_string + self._function_string if my_shape[1] != 1: - self._function_string = self._function_string.replace(top_var_name,"J") - self._function_string += "\nreturn nothing\nend\nend\nend" - self._function_string = "function {}(J, y, p, t)\n".format(funcname+"_with_consts") + self._function_string + self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) + self._function_string = "function {}(J, y, p, t)\n".format(funcname+"with_consts") + self._function_string elif self._dae_type=="semi-explicit": - self._function_string = self._function_string.replace(top_var_name,"dy") - self._function_string += "\nreturn nothing\nend\nend\nend" - self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string + self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) + self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string elif self._dae_type=="implicit": - self._function_string = self._function_string.replace(top_var_name,"out") - self._function_string += "\nreturn nothing\nend\nend\nend" - self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string + self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\end".format(top_var_name) + self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string return 0 @@ -681,30 +678,19 @@ def __init__(self,input,output,index): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] - inline = converter._inline & inline input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) index = self.index if type(index) is int: - rhs= "{}[{}]".format(input_var_name,index+1) + return "{}[{}]".format(input_var_name,index+1) elif type(index) is slice: if index.step is None: - rhs= "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) + return "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) elif type(index.step) is int: - rhs= "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) + return "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) else: raise NotImplementedError("Step has to be an integer.") else: raise NotImplementedError("Step must be a slice or an int") - - if inline: - return rhs - else: - result_var_name = converter.create_cache(self) - code = "@. {} = {}\n".format(result_var_name,rhs) - converter._function_string+=code - return result_var_name - - From f542f0811e8148df6f23590c9e73dcfd97028423 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 19 Sep 2022 16:46:20 -0400 Subject: [PATCH 081/163] revert --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 8f4ad870c2..84719c7647 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -471,7 +471,7 @@ def write_function_easy(self,funcname,inline=True): self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string elif self._dae_type=="implicit": - self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\end".format(top_var_name) + self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string return 0 From ea16c08f4483cb70f77e5e2b5b73eaccc43fb0b7 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 12:02:37 -0400 Subject: [PATCH 082/163] eliminate unwieldy final line --- .../operations/evaluate_julia.py | 78 +++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 84719c7647..f15f895c5d 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -372,8 +372,8 @@ def create_cache(self,symbol): my_id = symbol.output cache_shape = self._intermediate[my_id].shape - cache_id = self._cache_id+1 - self._cache_id = cache_id + cache_id = self._cache_id + self._cache_id += 1 cache_name = "cache_{}".format(cache_id) if self._cache_type=="standard": @@ -452,7 +452,9 @@ def clear(self): def write_function_easy(self,funcname,inline=True): #start with the closure top = self._intermediate[next(reversed(self._intermediate))] + #this line actually writes the code top_var_name = top._convert_intermediate_to_code(self,inline=False) + #write the cache initialization self._cache_and_const_string = "begin\n{} = let cs = (\n".format(funcname) + self._cache_and_const_string self._cache_and_const_string += ")\n" my_shape = top.shape @@ -465,14 +467,20 @@ def write_function_easy(self,funcname,inline=True): self._function_string = parameter_string + self._function_string self._function_string = self._cache_initialization_string + self._function_string if my_shape[1] != 1: - self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) - self._function_string = "function {}(J, y, p, t)\n".format(funcname+"with_consts") + self._function_string + self._function_string = self._function_string.replace(top_var_name,"J") + self._function_string += "\nreturn nothing\nend\nend\nend" + #self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) + self._function_string = "function {}(J, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="semi-explicit": - self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) - self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string + self._function_string = self._function_string.replace(top_var_name,"dy") + self._function_string += "\nreturn nothing\nend\nend\nend" + #self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) + self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="implicit": - self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) - self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"with_consts") + self._function_string + self._function_string = self._function_string.replace(top_var_name,"out") + self._function_string += "\nreturn nothing\nend\nend\nend" + #self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) + self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string return 0 @@ -546,15 +554,16 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) if not inline: result_var_name = converter.create_cache(self) + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) if converter._preallocate: code = "@. {} = {} {} {}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) else: code = "{} = {} .{} {})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) converter._function_string+=code elif inline: + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) result_var_name = "({} {} {})".format(left_input_var_name,self.operator,right_input_var_name) return result_var_name @@ -585,16 +594,17 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) if not inline: result_var_name = converter.create_cache(self) + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) if converter._preallocate: code = "@. {} = {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) else: code = "{} = {}.({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) converter._function_string+=code elif inline: + left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) result_var_name = "{}({},{})".format(self.name,left_input_var_name,right_input_var_name) return result_var_name @@ -613,9 +623,9 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) if not inline: result_var_name = converter.create_cache(self) + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) if converter._preallocate: code = "@. {} = {}({})\n".format(result_var_name,self.name,input_var_name) else: @@ -623,6 +633,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): converter._function_string+=code else: #assume an @. has already been issued + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) result_var_name = "({}({}))".format(self.name,input_var_name) return result_var_name @@ -631,15 +642,17 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + if not inline: result_var_name = converter.create_cache(self) + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) if converter._preallocate: code = "@. {} = - {}\n".format(result_var_name,input_var_name) else: code = "{} = -{}\n".format(result_var_name,input_var_name) converter._function_string+=code else: + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) result_var_name = "(- {})".format(input_var_name) return result_var_name @@ -647,8 +660,8 @@ class JuliaMinimumMaximum(JuliaBroadcastableFunction): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) result_var_name = converter.create_cache(self) + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) if converter._preallocate: code = "{} .= {}({})\n".format(result_var_name,self.name,input_var_name) else: @@ -678,19 +691,38 @@ def __init__(self,input,output,index): def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) index = self.index - if type(index) is int: - return "{}[{}]".format(input_var_name,index+1) - elif type(index) is slice: - if index.step is None: - return "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) - elif type(index.step) is int: - return "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) + inline = inline & converter._inline + if inline: + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + if type(index) is int: + return "{}[{}]".format(input_var_name,index+1) + elif type(index) is slice: + if index.step is None: + return "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) + elif type(index.step) is int: + return "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) + else: + raise NotImplementedError("Step has to be an integer.") else: - raise NotImplementedError("Step has to be an integer.") + raise NotImplementedError("Step must be a slice or an int") else: - raise NotImplementedError("Step must be a slice or an int") + result_var_name = converter.create_cache(self) + input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + if type(index) is int: + code = "@. {} = {}[{}]".format(result_var_name,input_var_name,index+1) + elif type(index) is slice: + if index.step is None: + code = "@. {} = (@view {}[{}:{}])".format(result_var_name,input_var_name,index.start+1,index.stop) + elif type(index.step) is int: + code = "@. {} = (@view {}[{}:{}:{}])".format(result_var_name,input_var_name,index.start+1,index.step,index.stop) + else: + raise NotImplementedError("Step has to be an integer.") + else: + raise NotImplementedError("Step must be a slice or an int") + converter._function_string+=code + return result_var_name + From 1d8ae2d38e7af4b62dcfe3514e532f922b96f5df Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 12:27:26 -0400 Subject: [PATCH 083/163] switch id to output, fix ics bug --- .../operations/evaluate_julia.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index f15f895c5d..9e5d75b5f1 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -406,7 +406,7 @@ def create_cache(self,symbol): def create_const(self,symbol): - my_id = symbol.id + my_id = symbol.output const_id = self._const_id+1 self._const_id = const_id const_name = "const_{}".format(const_id) @@ -732,16 +732,23 @@ class JuliaValue(object): class JuliaConstant(JuliaValue): def __init__(self,id,value): - self.id = id + self.output = id self.value = value self.shape = value.shape def _convert_intermediate_to_code(self,converter,inline=True): converter.create_const(self) - return converter._const_dict[self.id] + my_name = converter._const_dict[self.output] + if inline: + return converter._const_dict[self.output] + else: + result_var_name = converter.create_cache(self) + code = "{} .= {}\n".format(result_var_name,my_name) + converter._function_string+=code + return result_var_name class JuliaStateVector(JuliaValue): def __init__(self,id,loc,shape): - self.id = id + self.output = id self.loc = loc self.shape = shape def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): @@ -763,7 +770,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): class JuliaScalar(JuliaConstant): def __init__(self,id,value): - self.id = id + self.output = id self.value = float(value) self.shape = (1,1) def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): @@ -771,14 +778,14 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): class JuliaTime(JuliaScalar): def __init__(self,id): - self.id = id + self.output = id self.shape = (1,1) def _convert_intermediate_to_code(self,converter,inline=True): return "t" class JuliaInput(JuliaScalar): def __init__(self,id,name): - self.id = id + self.output = id self.shape = (1,1) self.name = name def _convert_intermediate_to_code(self,converter,inline=True): From 8fda73fd04434d9e2e90f781453896475a6ecd02 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 14:09:58 -0400 Subject: [PATCH 084/163] remove lines with top variable --- pybamm/expression_tree/operations/evaluate_julia.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 9e5d75b5f1..2f93d4eddf 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -25,6 +25,17 @@ def is_constant_and_can_evaluate(symbol): else: return False +def remove_lines_with(input_string,pattern): + string_list = input_string.split("\n") + my_string = "" + for s in string_list: + if pattern not in s: + my_string = my_string+s+"\n" + return my_string + + + + class JuliaConverter(object): def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit",input_parameter_order=[]): @@ -457,6 +468,8 @@ def write_function_easy(self,funcname,inline=True): #write the cache initialization self._cache_and_const_string = "begin\n{} = let cs = (\n".format(funcname) + self._cache_and_const_string self._cache_and_const_string += ")\n" + self._cache_and_const_string = remove_lines_with(self._cache_and_const_string,top_var_name) + self._cache_initialization_string = remove_lines_with(self._cache_initialization_string,top_var_name) my_shape = top.shape if len(self.input_parameter_order) != 0: parameter_string = "" From 01f4789fa2f505e681c5cf5b14a036461f183c88 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 17:09:31 -0400 Subject: [PATCH 085/163] jacobian alloc --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index c2499b429f..07f5f91c4f 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1167,7 +1167,7 @@ def generate_julia_diffeq( size_state = self.concatenated_initial_conditions.size state_vector = pybamm.StateVector(slice(0,size_state)) expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) - jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order) + jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type) jac_converter.convert_tree_to_intermediate(expr) jac_str = jac_converter.build_julia_code(funcname="jac_"+name) return eqn_str,ics_str,jac_str From d17ccd6fddd88c1dd5cd7f328d7fbc01e5f704b1 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 17:24:15 -0400 Subject: [PATCH 086/163] remove named tuple --- .../operations/evaluate_julia.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 2f93d4eddf..d2c3f52ac5 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -392,23 +392,23 @@ def create_cache(self,symbol): cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+="{} = zeros{},\n".format(cache_name,cache_shape_st) - self._cache_dict[symbol.output] = "cs."+cache_name + self._cache_and_const_string+="{} = zeros{}\n".format(cache_name,cache_shape_st) + self._cache_dict[symbol.output] = cache_name elif self._cache_type=="dual": if cache_shape[1] == 1: cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+="{} = dualcache(zeros{},12),\n".format(cache_name,cache_shape_st) - self._cache_initialization_string+=" {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_and_const_string+="{} = dualcache(zeros{},12)\n".format(cache_name,cache_shape_st) + self._cache_initialization_string+=" {} = PreallocationTools.get_tmp({},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name elif self._cache_type=="symbolic": if cache_shape[1]==1: cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{})),\n".format(cache_name, cache_shape_st,cache_shape[0]) - self._cache_initialization_string+=" {} = PyBaMM.get_tmp(cs.{},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format(cache_name, cache_shape_st,cache_shape[0]) + self._cache_initialization_string+=" {} = PyBaMM.get_tmp({},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name else: raise NotImplementedError("The cache type you've specified has not yet been implemented") @@ -421,10 +421,10 @@ def create_const(self,symbol): const_id = self._const_id+1 self._const_id = const_id const_name = "const_{}".format(const_id) - self._const_dict[my_id] = "cs."+const_name + self._const_dict[my_id] = const_name mat_value = symbol.value val_line = self.write_const(mat_value) - const_line = const_name+" = {},\n".format(val_line) + const_line = const_name+" = {}\n".format(val_line) self._cache_and_const_string+=const_line return 0 @@ -466,8 +466,7 @@ def write_function_easy(self,funcname,inline=True): #this line actually writes the code top_var_name = top._convert_intermediate_to_code(self,inline=False) #write the cache initialization - self._cache_and_const_string = "begin\n{} = let cs = (\n".format(funcname) + self._cache_and_const_string - self._cache_and_const_string += ")\n" + self._cache_and_const_string = "begin\n{} = let \n".format(funcname) + self._cache_and_const_string self._cache_and_const_string = remove_lines_with(self._cache_and_const_string,top_var_name) self._cache_initialization_string = remove_lines_with(self._cache_initialization_string,top_var_name) my_shape = top.shape From 9a6b5e5832fd6e9cc45fe68ef76d27078a779e70 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 17:57:00 -0400 Subject: [PATCH 087/163] revert silly constant thing, change ic --- pybamm/expression_tree/operations/evaluate_julia.py | 1 + pybamm/models/base_model.py | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index d2c3f52ac5..e6385238a4 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -750,6 +750,7 @@ def __init__(self,id,value): def _convert_intermediate_to_code(self,converter,inline=True): converter.create_const(self) my_name = converter._const_dict[self.output] + inline=True if inline: return converter._const_dict[self.output] else: diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 07f5f91c4f..a2b77050b7 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1156,12 +1156,6 @@ def generate_julia_diffeq( get_consistent_ics_solver.set_up(self) get_consistent_ics_solver._set_initial_conditions(self, {}, False) ics = pybamm.Vector(self.y0.full()) - ics_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type) - ics_converter.convert_tree_to_intermediate(ics) - ics_str = ics_converter.build_julia_code(funcname=name+"_u0") - # Change the string to a form for u0 - ics_str = ics_str.replace("(dy, y, p, t)", "(u0, p)") - ics_str = ics_str.replace("dy", "u0") if generate_jacobian: size_state = self.concatenated_initial_conditions.size @@ -1170,9 +1164,9 @@ def generate_julia_diffeq( jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type) jac_converter.convert_tree_to_intermediate(expr) jac_str = jac_converter.build_julia_code(funcname="jac_"+name) - return eqn_str,ics_str,jac_str + return eqn_str,ics,jac_str - return eqn_str, ics_str + return eqn_str, ics def latexify(self, filename=None, newline=True): # For docstring, see pybamm.expression_tree.operations.latexify.Latexify From d6c581b7d11d2a7394b3122ac43af7a7d30543b5 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 23:29:35 -0400 Subject: [PATCH 088/163] change name for dual and symbolic caches --- pybamm/expression_tree/operations/evaluate_julia.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index e6385238a4..535b3851af 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -399,16 +399,16 @@ def create_cache(self,symbol): cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+="{} = dualcache(zeros{},12)\n".format(cache_name,cache_shape_st) - self._cache_initialization_string+=" {} = PreallocationTools.get_tmp({},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_and_const_string+="{}_init = dualcache(zeros{},12)\n".format(cache_name,cache_shape_st) + self._cache_initialization_string+=" {} = PreallocationTools.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name elif self._cache_type=="symbolic": if cache_shape[1]==1: cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+=" {} = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format(cache_name, cache_shape_st,cache_shape[0]) - self._cache_initialization_string+=" {} = PyBaMM.get_tmp({},(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_and_const_string+=" {}_init = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format(cache_name, cache_shape_st,cache_shape[0]) + self._cache_initialization_string+=" {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name else: raise NotImplementedError("The cache type you've specified has not yet been implemented") From f3aa9417810dec50c830a37ef69d4071891260fd Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 20 Sep 2022 23:58:42 -0400 Subject: [PATCH 089/163] add gpu --- .../operations/evaluate_julia.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 535b3851af..25b4c1e414 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -410,6 +410,15 @@ def create_cache(self,symbol): self._cache_and_const_string+=" {}_init = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format(cache_name, cache_shape_st,cache_shape[0]) self._cache_initialization_string+=" {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name + elif self._cache_type=="gpu": + if cache_shape[1]==1: + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+="{} = CUDA.zeros{}\n".format(cache_name, cache_shape_st,cache_shape[0]) + self._cache_initialization_string+=" {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name + else: raise NotImplementedError("The cache type you've specified has not yet been implemented") return self._cache_dict[my_id] @@ -424,7 +433,7 @@ def create_const(self,symbol): self._const_dict[my_id] = const_name mat_value = symbol.value val_line = self.write_const(mat_value) - const_line = const_name+" = {}\n".format(val_line) + const_line = const_name+" = cu({})\n".format(val_line) self._cache_and_const_string+=const_line return 0 @@ -749,15 +758,7 @@ def __init__(self,id,value): self.shape = value.shape def _convert_intermediate_to_code(self,converter,inline=True): converter.create_const(self) - my_name = converter._const_dict[self.output] - inline=True - if inline: - return converter._const_dict[self.output] - else: - result_var_name = converter.create_cache(self) - code = "{} .= {}\n".format(result_var_name,my_name) - converter._function_string+=code - return result_var_name + return converter._const_dict[self.output] class JuliaStateVector(JuliaValue): def __init__(self,id,loc,shape): From 2ab543070628621882bed60a4a19725bf6ec6c6a Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 21 Sep 2022 11:36:45 -0400 Subject: [PATCH 090/163] typo --- pybamm/expression_tree/operations/evaluate_julia.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 25b4c1e414..69149d043d 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -416,7 +416,6 @@ def create_cache(self,symbol): else: cache_shape_st = cache_shape self._cache_and_const_string+="{} = CUDA.zeros{}\n".format(cache_name, cache_shape_st,cache_shape[0]) - self._cache_initialization_string+=" {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) self._cache_dict[symbol.output] = cache_name else: @@ -433,7 +432,10 @@ def create_const(self,symbol): self._const_dict[my_id] = const_name mat_value = symbol.value val_line = self.write_const(mat_value) - const_line = const_name+" = cu({})\n".format(val_line) + if self._cache_type=="gpu": + const_line = const_name+" = cu({})\n".format(val_line) + else: + const_line = const_name+" = {}\n".format(val_line) self._cache_and_const_string+=const_line return 0 @@ -532,7 +534,7 @@ def build_julia_code(self,funcname="f",inline=True): #BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH class JuliaBinaryOperation(object): - def __init__(self,left_input,right_input,output,shape): + def __init__(self,left_input,right_input,output,shape,branch): self.left_input = left_input self.right_input = right_input self.output = output @@ -544,7 +546,7 @@ def get_binary_inputs(self,converter:JuliaConverter,inline=True): #MatMul and Inner Product are not really the same as the bitwisebinary operations. class JuliaMatrixMultiplication(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape): + def __init__(self,left_input,right_input,output,shape,branch): self.left_input = left_input self.right_input = right_input self.output = output From a03ac99dbae2cbd879d35d2420bc2462c84ca743 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 21 Sep 2022 11:42:42 -0400 Subject: [PATCH 091/163] typo --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 69149d043d..001221eafc 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -546,7 +546,7 @@ def get_binary_inputs(self,converter:JuliaConverter,inline=True): #MatMul and Inner Product are not really the same as the bitwisebinary operations. class JuliaMatrixMultiplication(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape,branch): + def __init__(self,left_input,right_input,output,shape): self.left_input = left_input self.right_input = right_input self.output = output From 8eeba2f84522db706e85e2e281137c4a16046fe6 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 21 Sep 2022 19:16:52 -0400 Subject: [PATCH 092/163] add inline option --- pybamm/models/base_model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index a2b77050b7..c905d03e19 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1106,6 +1106,7 @@ def generate_julia_diffeq( dae_type="semi-explicit", generate_jacobian=False, cache_type="standard", + inline=True, **kwargs, ): """ @@ -1137,7 +1138,7 @@ def generate_julia_diffeq( if self.algebraic == {}: # ODE model: form dy[] = ... - converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type) + converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline) converter.convert_tree_to_intermediate(self.concatenated_rhs) eqn_str = converter.build_julia_code(funcname=name) else: @@ -1146,7 +1147,7 @@ def generate_julia_diffeq( else: len_rhs = self.concatenated_rhs.size - converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order,cache_type=cache_type) + converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline) converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic),len_rhs=len_rhs) eqn_str = converter.build_julia_code(funcname=name) @@ -1161,7 +1162,7 @@ def generate_julia_diffeq( size_state = self.concatenated_initial_conditions.size state_vector = pybamm.StateVector(slice(0,size_state)) expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) - jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type) + jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline) jac_converter.convert_tree_to_intermediate(expr) jac_str = jac_converter.build_julia_code(funcname="jac_"+name) return eqn_str,ics,jac_str From a41f0448ee2e0e63c61efc1c01cb8347cac70731 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 21 Sep 2022 19:33:26 -0400 Subject: [PATCH 093/163] begin preallocate=false --- .../operations/evaluate_julia.py | 82 ++++++++++--------- pybamm/models/base_model.py | 7 +- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 001221eafc..d800329e27 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -381,46 +381,48 @@ def _convert_tree_to_intermediate(self,symbol:pybamm.StateVectorDot): #Cache and Const Creation def create_cache(self,symbol): my_id = symbol.output - - cache_shape = self._intermediate[my_id].shape - cache_id = self._cache_id - self._cache_id += 1 - cache_name = "cache_{}".format(cache_id) - - if self._cache_type=="standard": - if cache_shape[1] == 1: - cache_shape_st = "({})".format(cache_shape[0]) - else: - cache_shape_st = cache_shape - self._cache_and_const_string+="{} = zeros{}\n".format(cache_name,cache_shape_st) - self._cache_dict[symbol.output] = cache_name - elif self._cache_type=="dual": - if cache_shape[1] == 1: - cache_shape_st = "({})".format(cache_shape[0]) - else: - cache_shape_st = cache_shape - self._cache_and_const_string+="{}_init = dualcache(zeros{},12)\n".format(cache_name,cache_shape_st) - self._cache_initialization_string+=" {} = PreallocationTools.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) - self._cache_dict[symbol.output] = cache_name - elif self._cache_type=="symbolic": - if cache_shape[1]==1: - cache_shape_st = "({})".format(cache_shape[0]) - else: - cache_shape_st = cache_shape - self._cache_and_const_string+=" {}_init = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format(cache_name, cache_shape_st,cache_shape[0]) - self._cache_initialization_string+=" {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) - self._cache_dict[symbol.output] = cache_name - elif self._cache_type=="gpu": - if cache_shape[1]==1: - cache_shape_st = "({})".format(cache_shape[0]) + if self._preallocate: + cache_shape = self._intermediate[my_id].shape + cache_id = self._cache_id + self._cache_id += 1 + cache_name = "cache_{}".format(cache_id) + + if self._cache_type=="standard": + if cache_shape[1] == 1: + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+="{} = zeros{}\n".format(cache_name,cache_shape_st) + self._cache_dict[symbol.output] = cache_name + elif self._cache_type=="dual": + if cache_shape[1] == 1: + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+="{}_init = dualcache(zeros{},12)\n".format(cache_name,cache_shape_st) + self._cache_initialization_string+=" {} = PreallocationTools.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name + elif self._cache_type=="symbolic": + if cache_shape[1]==1: + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+=" {}_init = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format(cache_name, cache_shape_st,cache_shape[0]) + self._cache_initialization_string+=" {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name + elif self._cache_type=="gpu": + if cache_shape[1]==1: + cache_shape_st = "({})".format(cache_shape[0]) + else: + cache_shape_st = cache_shape + self._cache_and_const_string+="{} = CUDA.zeros{}\n".format(cache_name, cache_shape_st,cache_shape[0]) + self._cache_dict[symbol.output] = cache_name else: - cache_shape_st = cache_shape - self._cache_and_const_string+="{} = CUDA.zeros{}\n".format(cache_name, cache_shape_st,cache_shape[0]) - self._cache_dict[symbol.output] = cache_name - + raise NotImplementedError("The cache type you've specified has not yet been implemented") + return self._cache_dict[my_id] else: - raise NotImplementedError("The cache type you've specified has not yet been implemented") - return self._cache_dict[my_id] + self._cache_dict[symbol.output] = cache_name + return self._cache_dict @@ -534,7 +536,7 @@ def build_julia_code(self,funcname="f",inline=True): #BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH class JuliaBinaryOperation(object): - def __init__(self,left_input,right_input,output,shape,branch): + def __init__(self,left_input,right_input,output,shape): self.left_input = left_input self.right_input = right_input self.output = output @@ -583,7 +585,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): if converter._preallocate: code = "@. {} = {} {} {}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) else: - code = "{} = {} .{} {})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) + code = "{} = @. ( {} {} {})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) converter._function_string+=code elif inline: left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index c905d03e19..d55fba3484 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1107,6 +1107,7 @@ def generate_julia_diffeq( generate_jacobian=False, cache_type="standard", inline=True, + preallocate=True **kwargs, ): """ @@ -1138,7 +1139,7 @@ def generate_julia_diffeq( if self.algebraic == {}: # ODE model: form dy[] = ... - converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline) + converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline,preallocate=preallocate) converter.convert_tree_to_intermediate(self.concatenated_rhs) eqn_str = converter.build_julia_code(funcname=name) else: @@ -1147,7 +1148,7 @@ def generate_julia_diffeq( else: len_rhs = self.concatenated_rhs.size - converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline) + converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline,preallocate=preallocate) converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic),len_rhs=len_rhs) eqn_str = converter.build_julia_code(funcname=name) @@ -1162,7 +1163,7 @@ def generate_julia_diffeq( size_state = self.concatenated_initial_conditions.size state_vector = pybamm.StateVector(slice(0,size_state)) expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) - jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline) + jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline,preallocate=preallocate) jac_converter.convert_tree_to_intermediate(expr) jac_str = jac_converter.build_julia_code(funcname="jac_"+name) return eqn_str,ics,jac_str From 406596168dd5ae60effa2101441a3dca9bd60384 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 21 Sep 2022 19:33:58 -0400 Subject: [PATCH 094/163] cmd-s --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index d800329e27..ac58d47ad7 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -591,7 +591,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) result_var_name = "({} {} {})".format(left_input_var_name,self.operator,right_input_var_name) return result_var_name - +#PREALLOCATING STOPPED HERE class JuliaAddition(JuliaBitwiseBinaryOperation): pass From 0a3477beed31cb9f1dfc137f7909be55d8590522 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Sun, 25 Sep 2022 14:48:36 -0400 Subject: [PATCH 095/163] typo --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index d55fba3484..a49aea3bd0 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1107,7 +1107,7 @@ def generate_julia_diffeq( generate_jacobian=False, cache_type="standard", inline=True, - preallocate=True + preallocate=True, **kwargs, ): """ From 2c55d29417952f5d0f6eec1c16e8b1626278315e Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Sun, 25 Sep 2022 15:12:13 -0400 Subject: [PATCH 096/163] typo --- pybamm/expression_tree/operations/evaluate_julia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index ac58d47ad7..369342f230 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -38,7 +38,7 @@ def remove_lines_with(input_string,pattern): class JuliaConverter(object): - def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit",input_parameter_order=[]): + def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit",input_parameter_order=[],inline=True): assert not ismtk #Characteristics @@ -49,7 +49,7 @@ def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",p self._dae_type = dae_type self._type = "Float64" - self._inline = True + self._inline = inline #"Caches" #Stores Constants to be Declared in the initial cache #insight: everything is just a line of code From e2660f4be406d64feab6b0ec4d2bd605cbdb27c4 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Tue, 27 Sep 2022 13:37:33 -0400 Subject: [PATCH 097/163] switch to juliacall --- .../operations/evaluate_julia.py | 11 +++-- requirements.txt | 2 +- .../test_operations/test_evaluate_julia.py | 41 +++++++------------ 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 369342f230..f9cff0b917 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -381,12 +381,11 @@ def _convert_tree_to_intermediate(self,symbol:pybamm.StateVectorDot): #Cache and Const Creation def create_cache(self,symbol): my_id = symbol.output + cache_shape = self._intermediate[my_id].shape + cache_id = self._cache_id + self._cache_id += 1 + cache_name = "cache_{}".format(cache_id) if self._preallocate: - cache_shape = self._intermediate[my_id].shape - cache_id = self._cache_id - self._cache_id += 1 - cache_name = "cache_{}".format(cache_id) - if self._cache_type=="standard": if cache_shape[1] == 1: cache_shape_st = "({})".format(cache_shape[0]) @@ -422,7 +421,7 @@ def create_cache(self,symbol): return self._cache_dict[my_id] else: self._cache_dict[symbol.output] = cache_name - return self._cache_dict + return self._cache_dict[symbol.output] diff --git a/requirements.txt b/requirements.txt index 75431982d9..831d301135 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ autograd >= 1.2 scikit-fem >= 0.2.0 casadi >= 3.5.0 imageio>=2.9.0 -julia>=0.5.6 +juliacall >= 0.1.0 jupyter # For example notebooks pybtex>=0.24.0 sympy >= 1.8 diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 0888c59be7..681e11e858 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -11,14 +11,11 @@ have_julia = pybamm.have_julia() if have_julia and system() != "Windows": - from julia.api import Julia - Julia(compiled_modules=False) - from julia import Main - from julia.core import JuliaError + from juliacall import Main # load julia libraries required for evaluating the strings - Main.eval("using SparseArrays, LinearAlgebra") + Main.seval("using SparseArrays, LinearAlgebra") @unittest.skipIf(not have_julia, "Julia not installed") @@ -32,39 +29,29 @@ def evaluate_and_test_equal( if not isinstance(t_tests, list): t_tests = [t_tests] if inputs is None: - input_parameter_order = None + input_parameter_order = [] p = 0.0 else: input_parameter_order = list(inputs.keys()) p = list(inputs.values()) pybamm_eval = expr.evaluate(t=t_tests[0], y=y_tests[0], inputs=inputs) - for preallocate in [True, False]: - kwargs["funcname"] = ( - kwargs.get("funcname", "f") + "_" + str(int(preallocate)) - ) - funcname = kwargs["funcname"] - myconverter = pybamm.JuliaConverter() + for preallocate in [True,False]: + myconverter = pybamm.JuliaConverter(preallocate=preallocate,input_parameter_order=input_parameter_order) myconverter.convert_tree_to_intermediate(expr) - evaluator_str = myconverter.build_julia_code(funcname=funcname) - Main.eval(evaluator_str) - funcname = kwargs.get("funcname", "f") - Main.p = p + evaluator_str = myconverter.build_julia_code(funcname="f") + Main.seval(evaluator_str) + p = p for t_test, y_test in zip(t_tests, y_tests): - Main.dy = np.zeros_like(pybamm_eval) - Main.y = y_test - Main.t = t_test - try: - Main.eval(f"{funcname}(dy,y,p,t)") - except JuliaError as e: - # debugging - #print(Main.dy, y_test, p, t_test) - #print(evaluator_str) - raise e + dy = np.zeros_like(pybamm_eval) + y = y_test + t = t_test + Main.f(dy, y, t, p) + pybamm_eval = expr.evaluate(t=t_test, y=y_test, inputs=inputs).flatten() try: np.testing.assert_array_almost_equal( - Main.dy.flatten(), + dy.flatten(), pybamm_eval, decimal=decimal, ) From 1ab606627b9093b5051dc33c9a5f61e355b65ba8 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Wed, 28 Sep 2022 10:26:12 -0400 Subject: [PATCH 098/163] fixing tests --- pybamm/expression_tree/operations/evaluate_julia.py | 3 --- .../test_operations/test_evaluate_julia.py | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index f9cff0b917..7de5968251 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -493,17 +493,14 @@ def write_function_easy(self,funcname,inline=True): if my_shape[1] != 1: self._function_string = self._function_string.replace(top_var_name,"J") self._function_string += "\nreturn nothing\nend\nend\nend" - #self._function_string += "J[:,:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(J, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="semi-explicit": self._function_string = self._function_string.replace(top_var_name,"dy") self._function_string += "\nreturn nothing\nend\nend\nend" - #self._function_string+= "dy[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="implicit": self._function_string = self._function_string.replace(top_var_name,"out") self._function_string += "\nreturn nothing\nend\nend\nend" - #self._function_string+="out[:] .= {}\nreturn nothing\nend\nend\nend".format(top_var_name) self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string return 0 diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 681e11e858..0403db7672 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -36,7 +36,7 @@ def evaluate_and_test_equal( p = list(inputs.values()) pybamm_eval = expr.evaluate(t=t_tests[0], y=y_tests[0], inputs=inputs) - for preallocate in [True,False]: + for preallocate in [True]: myconverter = pybamm.JuliaConverter(preallocate=preallocate,input_parameter_order=input_parameter_order) myconverter.convert_tree_to_intermediate(expr) evaluator_str = myconverter.build_julia_code(funcname="f") @@ -46,7 +46,7 @@ def evaluate_and_test_equal( dy = np.zeros_like(pybamm_eval) y = y_test t = t_test - Main.f(dy, y, t, p) + Main.f(dy, y, p, t) pybamm_eval = expr.evaluate(t=t_test, y=y_test, inputs=inputs).flatten() try: @@ -166,7 +166,7 @@ def test_evaluator_julia_input_parameters(self): # test one input parameter expr = a * c - self.evaluate_and_test_equal(expr, np.array([2.0, 3.0]), inputs={"c": 5}) + self.evaluate_and_test_equal(expr, np.array([2.0]), inputs={"c": 5}) # test several input parameters expr = a * c + b * d From ff5b5a81cd6293e1c1a92cefc734ff21de4cec6a Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Wed, 28 Sep 2022 13:49:32 -0400 Subject: [PATCH 099/163] pass tests --- .../operations/evaluate_julia.py | 68 ++++++++++++++----- .../test_operations/test_evaluate_julia.py | 23 +++++-- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 7de5968251..ef8bb91bc7 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -486,21 +486,24 @@ def write_function_easy(self,funcname,inline=True): parameter_string = "" for parameter in self.input_parameter_order: parameter_string+="{},".format(parameter) - parameter_string = parameter_string[0:-1] parameter_string += "= p\n" self._function_string = parameter_string + self._function_string self._function_string = self._cache_initialization_string + self._function_string + if self._preallocate: + self._function_string += "\n return nothing\n" + else: + self._function_string += "\n return {}\n".format(top_var_name) if my_shape[1] != 1: self._function_string = self._function_string.replace(top_var_name,"J") - self._function_string += "\nreturn nothing\nend\nend\nend" + self._function_string += "end\nend\nend" self._function_string = "function {}(J, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="semi-explicit": self._function_string = self._function_string.replace(top_var_name,"dy") - self._function_string += "\nreturn nothing\nend\nend\nend" + self._function_string += "end\nend\nend" self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string elif self._dae_type=="implicit": self._function_string = self._function_string.replace(top_var_name,"out") - self._function_string += "\nreturn nothing\nend\nend\nend" + self._function_string += "end\nend\nend" self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string return 0 @@ -558,7 +561,7 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=False): if converter._preallocate: code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) else: - code = "{} = {} * {}".format(result_var_name,left_input_var_name,right_input_var_name) + code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) converter._function_string+=code return result_var_name @@ -838,14 +841,20 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): code = "" elif child_var.shape[0] == 1: end_row = 1 - if vec: - code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) + if converter._preallocate: + if vec: + code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) + else: + code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) else: - code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "{} = vcat({}".format(my_name,child_var_name) else: start_row = 1 end_row = child_var.shape[0] - code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + if converter._preallocate: + code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + else: + code = "{} = vcat({}".format(my_name,child_var_name) for child in self.children[1:]: child_var = converter._intermediate[child] @@ -855,14 +864,24 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): elif child_var.shape[0] == 1: start_row = end_row+1 end_row = start_row+1 - if vec: - code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + if converter._preallocate: + if vec: + code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + else: + code += "@. {}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + elif child==self.children[-1]: + code += ",{})\n".format(child_var_name) else: - code += "@. {}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code += ",{}".format(child_var_name) else: start_row = end_row+1 - end_row = start_row+child_var.shape[0]-1 - code += "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + end_row = start_row+child_var.shape[0]-1 + if converter._preallocate: + code += "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + elif child==self.children[-1]: + code += ",{})\n".format(child_var_name) + else: + code += ",{}".format(child_var_name) converter._function_string+=code return my_name @@ -907,7 +926,16 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): stop = this_slice.stop start_row = end_row+1 end_row = start_row+(stop-start)-1 - code += "@. {}[{}:{}{} = {}\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name) + if converter._preallocate: + code += "@. {}[{}:{}{} = {}\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name) + else: + if c==0: + code+="{} = vcat({}".format(result_var_name,child_var_name) + elif c==len(self.children)-1: + code+=",{})\n".format(child_var_name) + else: + code +=",{}".format(child_var_name) + else: for i in range(self.secondary_dimension_npts): for c in range(len(self.children)): @@ -918,7 +946,15 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): stop = this_slice.stop start_row = end_row+1 end_row = start_row+(stop-start)-1 - code += "@. {}[{}:{}{} = (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + if converter._preallocate: + code += "@. {}[{}:{}{} = (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + else: + if (c==0) & (i==0): + code+="{} = vcat((@view {}[{}:{}{})".format(result_var_name,child_var_name,start+1,stop,right_parenthesis) + elif (c==len(self.children)-1) & (i==self.secondary_dimension_npts-1): + code+=",(@view {}[{}:{}{}))\n".format(child_var_name,start+1,stop,right_parenthesis) + else: + code +=",(@view {}[{}:{}{})".format(child_var_name,start+1,stop,right_parenthesis) converter._function_string+=code return result_var_name diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 0403db7672..b6bf98547e 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -13,6 +13,7 @@ if have_julia and system() != "Windows": from juliacall import Main + from juliacall import JuliaError # load julia libraries required for evaluating the strings Main.seval("using SparseArrays, LinearAlgebra") @@ -33,20 +34,29 @@ def evaluate_and_test_equal( p = 0.0 else: input_parameter_order = list(inputs.keys()) - p = list(inputs.values()) + p = np.array(list(inputs.values())) pybamm_eval = expr.evaluate(t=t_tests[0], y=y_tests[0], inputs=inputs) - for preallocate in [True]: + for preallocate in [True,False]: myconverter = pybamm.JuliaConverter(preallocate=preallocate,input_parameter_order=input_parameter_order) myconverter.convert_tree_to_intermediate(expr) evaluator_str = myconverter.build_julia_code(funcname="f") - Main.seval(evaluator_str) - p = p + try: + Main.seval(evaluator_str) + except JuliaError as e: + text_file = open("julia_evaluator_{}.jl".format(kwargs["funcname"]), "w") + text_file.write(evaluator_str) + text_file.close() + for t_test, y_test in zip(t_tests, y_tests): dy = np.zeros_like(pybamm_eval) y = y_test t = t_test - Main.f(dy, y, p, t) + if preallocate: + Main.f(dy, y, p, t) + else: + dy = Main.f(dy, y, p, t) + dy = np.array(dy) pybamm_eval = expr.evaluate(t=t_test, y=y_test, inputs=inputs).flatten() try: @@ -59,6 +69,9 @@ def evaluate_and_test_equal( # debugging #print(Main.dy, y_test, p, t_test) #print(evaluator_str) + text_file = open("julia_evaluator_{}.jl".format(kwargs["funcname"]), "w") + text_file.write(evaluator_str) + text_file.close() raise e def test_exceptions(self): From 0d459bee8f8f3d573b2bfba9e4b64a52f3c10d66 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Wed, 28 Sep 2022 15:16:08 -0400 Subject: [PATCH 100/163] style --- .../operations/evaluate_julia.py | 1060 ++++++++++------- pybamm/models/base_model.py | 41 +- .../test_operations/test_evaluate_julia.py | 31 +- 3 files changed, 700 insertions(+), 432 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index ef8bb91bc7..7b01ddb9eb 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -4,12 +4,12 @@ import pybamm import numpy as np import numpy -from scipy import special import scipy from collections import OrderedDict from multimethod import multimethod from math import floor + def is_constant_and_can_evaluate(symbol): """ Returns True if symbol is constant and evaluation does not raise any errors. @@ -25,73 +25,86 @@ def is_constant_and_can_evaluate(symbol): else: return False -def remove_lines_with(input_string,pattern): + +def remove_lines_with(input_string, pattern): string_list = input_string.split("\n") my_string = "" for s in string_list: if pattern not in s: - my_string = my_string+s+"\n" + my_string = my_string + s + "\n" return my_string - - - class JuliaConverter(object): - def __init__(self,ismtk=False,cache_type="standard",jacobian_type="analytical",preallocate=True,dae_type="semi-explicit",input_parameter_order=[],inline=True): + def __init__( + self, + ismtk=False, + cache_type="standard", + jacobian_type="analytical", + preallocate=True, + dae_type="semi-explicit", + input_parameter_order=[], + inline=True, + ): assert not ismtk - #Characteristics + # Characteristics self._cache_type = cache_type - self._ismtk=ismtk - self._jacobian_type=jacobian_type - self._preallocate=preallocate + self._ismtk = ismtk + self._jacobian_type = jacobian_type + self._preallocate = preallocate self._dae_type = dae_type self._type = "Float64" self._inline = inline - #"Caches" - #Stores Constants to be Declared in the initial cache - #insight: everything is just a line of code - - #INTERMEDIATE: A List of Stuff to do. Keys are ID's and lists are variable names. + # "Caches" + # Stores Constants to be Declared in the initial cache + # insight: everything is just a line of code + + # INTERMEDIATE: A List of Stuff to do. + # Keys are ID's and lists are variable names. self._intermediate = OrderedDict() - #Cache Dict and Const Dict Host Julia Variable Names to be used to generate the code. + # Cache Dict and Const Dict Host Julia + # Variable Names to be used to generate the code. self._cache_dict = OrderedDict() self._const_dict = OrderedDict() self.input_parameter_order = input_parameter_order - + self._cache_id = 0 self._const_id = 0 - + self._cache_and_const_string = "" self.function_definition = "" self._function_string = "" self._return_string = "" self._cache_initialization_string = "" - - def cache_exists(self,id): - return (self._cache_dict.get(id)!=None) - - - #know where to go to find a variable. this could be smoother, there will need to be a ton of boilerplate here. - #This function breaks down and analyzes any binary tree. Will fail if used on a non-binary tree. - def break_down_binary(self,symbol): - #Check for constant - #assert not is_constant_and_can_evaluate(symbol) - - #We know that this should only have 2 children - assert len(symbol.children)==2 - - #take care of the kids first (this is recursive but multiple-dispatch recursive which is cool) + + def cache_exists(self, id): + return self._cache_dict.get(id) is not None + + # know where to go to find a variable. + # this could be smoother, there will + # need to be a ton of boilerplate here. + # This function breaks down and analyzes + # any binary tree. Will fail if used on + # a non-binary tree. + def break_down_binary(self, symbol): + # Check for constant + # assert not is_constant_and_can_evaluate(symbol) + + # We know that this should only have 2 children + assert len(symbol.children) == 2 + + # take care of the kids first (this is recursive + # but multiple-dispatch recursive which is cool) id_left = self._convert_tree_to_intermediate(symbol.children[0]) id_right = self._convert_tree_to_intermediate(symbol.children[1]) my_id = symbol.id - return id_left,id_right,my_id - - def break_down_concatenation(self,symbol): + return id_left, id_right, my_id + + def break_down_concatenation(self, symbol): child_ids = [] for child in symbol.children: child_id = self._convert_tree_to_intermediate(child) @@ -102,350 +115,405 @@ def break_down_concatenation(self,symbol): for child_id in child_ids: child_shape = self._intermediate[child_id].shape assert num_cols == child_shape[1] - num_rows+=child_shape[0] - shape = (num_rows,num_cols) - return child_ids,shape + num_rows += child_shape[0] + shape = (num_rows, num_cols) + return child_ids, shape - - #Convert-Trees go here + # Convert-Trees go here - #Binary trees constructors. All follow the pattern of mat-mul. They need to find their shapes, assuming that the shapes of the nodes one level below them in the expression tree have already been computed. + # Binary trees constructors. All follow the pattern of mat-mul. + # They need to find their shapes, assuming that the shapes of + # the nodes one level below them in the expression tree have + # already been computed. @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.NumpyConcatenation): + def _convert_tree_to_intermediate(self, symbol: pybamm.NumpyConcatenation): my_id = symbol.id - children_julia,shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaNumpyConcatenation(my_id,shape,children_julia) + children_julia, shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaNumpyConcatenation( + my_id, shape, children_julia + ) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.SparseStack): + def _convert_tree_to_intermediate(self, symbol: pybamm.SparseStack): my_id = symbol.id - children_julia,shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaSparseStack(my_id,shape,children_julia) + children_julia, shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaSparseStack(my_id, shape, children_julia) return my_id @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.DomainConcatenation): + def _convert_tree_to_intermediate(self, symbol: pybamm.DomainConcatenation): my_id = symbol.id - children_julia,shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaDomainConcatenation(my_id,shape,children_julia,symbol.secondary_dimensions_npts,symbol._children_slices) + children_julia, shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaDomainConcatenation( + my_id, + shape, + children_julia, + symbol.secondary_dimensions_npts, + symbol._children_slices, + ) return my_id - @multimethod - def _convert_tree_to_intermediate(self,symbol: pybamm.MatrixMultiplication): - #Break down the binary tree - id_left,id_right,my_id = self.break_down_binary(symbol) + def _convert_tree_to_intermediate(self, symbol: pybamm.MatrixMultiplication): + # Break down the binary tree + id_left, id_right, my_id = self.break_down_binary(symbol) left_shape = self._intermediate[id_left].shape right_shape = self._intermediate[id_right].shape - my_shape = (left_shape[0],right_shape[1]) - #Cache the result. - self._intermediate[my_id] = JuliaMatrixMultiplication(id_left,id_right,my_id,my_shape) + my_shape = (left_shape[0], right_shape[1]) + # Cache the result. + self._intermediate[my_id] = JuliaMatrixMultiplication( + id_left, id_right, my_id, my_shape + ) return my_id - - @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Multiplication): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape,"*") + + @multimethod + def _convert_tree_to_intermediate(self, symbol: pybamm.Multiplication): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaMultiplication( + id_left, id_right, my_id, my_shape, "*" + ) return my_id - - #Apparently an inner product is a hadamard product in pybamm - @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Inner): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMultiplication(id_left,id_right,my_id,my_shape,"*") + + # Apparently an inner product is a hadamard product in pybamm + @multimethod + def _convert_tree_to_intermediate(self, symbol: pybamm.Inner): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaMultiplication( + id_left, id_right, my_id, my_shape, "*" + ) return my_id - - @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Division): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaDivision(id_left,id_right,my_id,my_shape,"/") + + @multimethod + def _convert_tree_to_intermediate(self, symbol: pybamm.Division): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaDivision( + id_left, id_right, my_id, my_shape, "/" + ) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol: pybamm.Addition): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaAddition(id_left,id_right,my_id,my_shape,"+") + def _convert_tree_to_intermediate(self, symbol: pybamm.Addition): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaAddition( + id_left, id_right, my_id, my_shape, "+" + ) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol: pybamm.Subtraction): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaSubtraction(id_left,id_right,my_id,my_shape,"-") + def _convert_tree_to_intermediate(self, symbol: pybamm.Subtraction): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaSubtraction( + id_left, id_right, my_id, my_shape, "-" + ) return my_id @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Minimum): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"min") + def _convert_tree_to_intermediate(self, symbol: pybamm.Minimum): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaMinMax( + id_left, id_right, my_id, my_shape, "min" + ) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Maximum): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaMinMax(id_left,id_right,my_id,my_shape,"max") + def _convert_tree_to_intermediate(self, symbol: pybamm.Maximum): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaMinMax( + id_left, id_right, my_id, my_shape, "max" + ) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Power): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaPower(id_left,id_right,my_id,my_shape,"^") + def _convert_tree_to_intermediate(self, symbol: pybamm.Power): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaPower(id_left, id_right, my_id, my_shape, "^") return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.EqualHeaviside): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<=") + def _convert_tree_to_intermediate(self, symbol: pybamm.EqualHeaviside): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation( + id_left, id_right, my_id, my_shape, "<=" + ) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.NotEqualHeaviside): - id_left,id_right,my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left,id_right) - self._intermediate[my_id] = JuliaBitwiseBinaryOperation(id_left,id_right,my_id,my_shape,"<") + def _convert_tree_to_intermediate(self, symbol: pybamm.NotEqualHeaviside): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation( + id_left, id_right, my_id, my_shape, "<" + ) return my_id - - @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Index): - assert len(symbol.children)==1 + + @multimethod + def _convert_tree_to_intermediate(self, symbol: pybamm.Index): + assert len(symbol.children) == 1 id_lower = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id index = symbol.index - self._intermediate[my_id] = JuliaIndex(id_lower,my_id,index) + self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index) return my_id - def find_broadcastable_shape(self,id_left,id_right): + def find_broadcastable_shape(self, id_left, id_right): left_shape = self._intermediate[id_left].shape right_shape = self._intermediate[id_right].shape - #check if either is a scalar - if left_shape == (1,1): + # check if either is a scalar + if left_shape == (1, 1): return right_shape - elif right_shape==(1,1): + elif right_shape == (1, 1): return left_shape - elif left_shape==right_shape: + elif left_shape == right_shape: return left_shape - elif (left_shape[0]==1) & (right_shape[1]==1): - return (right_shape[0],left_shape[1]) - elif (right_shape[0]==1) & (left_shape[1]==1): - return (left_shape[0],right_shape[1]) - elif (right_shape[0]==1) & (right_shape[1]==left_shape[1]): + elif (left_shape[0] == 1) & (right_shape[1] == 1): + return (right_shape[0], left_shape[1]) + elif (right_shape[0] == 1) & (left_shape[1] == 1): + return (left_shape[0], right_shape[1]) + elif (right_shape[0] == 1) & (right_shape[1] == left_shape[1]): return left_shape - elif (left_shape[0]==1) & (right_shape[1]==left_shape[1]): + elif (left_shape[0] == 1) & (right_shape[1] == left_shape[1]): return right_shape - elif (right_shape[1]==1) & (right_shape[0]==left_shape[0]): + elif (right_shape[1] == 1) & (right_shape[0] == left_shape[0]): return left_shape - elif (left_shape[1]==1) & (right_shape[0]==left_shape[0]): + elif (left_shape[1] == 1) & (right_shape[0] == left_shape[0]): return right_shape else: print("Right type is {}".format(type(self._intermediate[id_right]))) print("Right Shape is {}".format(right_shape)) print("Left Shape is {}".format(left_shape)) - raise NotImplementedError("multiplication for the shapes youve requested doesnt work.") - - - #to find the shape, there are a number of elements that should just have the shame shape as their children. This function removes boilerplate by implementing those cases - def same_shape(self,id_left,id_right): + raise NotImplementedError( + "multiplication for the shapes youve requested doesnt work." + ) + + # to find the shape, there are a number of elements that should just + # have the shame shape as their children. This function removes + # boilerplate by implementing those cases + def same_shape(self, id_left, id_right): left_shape = self._intermediate[id_left].shape right_shape = self._intermediate[id_right].shape - assert left_shape==right_shape - return left_shape - - - #Functions - #Broadcastable functions have 1 input and 1 output, and the input and output have the same shape. The hard part is that we have to know which is which and pybamm doesn't differentiate between the two. So, we have to do that with an if statement. + assert left_shape == right_shape + return left_shape + + # Functions + # Broadcastable functions have 1 input and 1 output, and the + # input and output have the same shape. The hard part is that + # we have to know which is which and pybamm doesn't differentiate + # between the two. So, we have to do that with an if statement. @multimethod - def _convert_tree_to_intermediate(self,symbol): - raise NotImplementedError( + def _convert_tree_to_intermediate(self, symbol): + raise NotImplementedError( "Conversion to Julia not implemented for a symbol of type '{}'".format( type(symbol) ) ) - @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Min): + @multimethod + def _convert_tree_to_intermediate(self, symbol: pybamm.Min): my_jl_name = symbol.julia_name - assert len(symbol.children)==1 - my_shape = (1,1) + assert len(symbol.children) == 1 + my_shape = (1, 1) input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id - self._intermediate[my_id] = JuliaMinimumMaximum(my_jl_name,input,my_id,my_shape) + self._intermediate[my_id] = JuliaMinimumMaximum( + my_jl_name, input, my_id, my_shape + ) return my_id - - @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Max): + + @multimethod + def _convert_tree_to_intermediate(self, symbol: pybamm.Max): my_jl_name = symbol.julia_name - assert len(symbol.children)==1 - my_shape = (1,1) + assert len(symbol.children) == 1 + my_shape = (1, 1) input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id - self._intermediate[my_id] = JuliaMinimumMaximum(my_jl_name,input,my_id,my_shape) + self._intermediate[my_id] = JuliaMinimumMaximum( + my_jl_name, input, my_id, my_shape + ) return my_id @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Function): + def _convert_tree_to_intermediate(self, symbol: pybamm.Function): my_jl_name = symbol.julia_name - assert len(symbol.children)==1 + assert len(symbol.children) == 1 my_shape = symbol.children[0].shape input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id - self._intermediate[my_id] = JuliaBroadcastableFunction(my_jl_name,input,my_id,my_shape) + self._intermediate[my_id] = JuliaBroadcastableFunction( + my_jl_name, input, my_id, my_shape + ) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Negate): + def _convert_tree_to_intermediate(self, symbol: pybamm.Negate): my_jl_name = "-" - assert len(symbol.children)==1 + assert len(symbol.children) == 1 my_shape = symbol.children[0].shape input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id - self._intermediate[my_id] = JuliaNegation(my_jl_name,input,my_id,my_shape) + self._intermediate[my_id] = JuliaNegation(my_jl_name, input, my_id, my_shape) return my_id - - - #Constants and Values. There are only 2 of these. They must know their own shapes. + # Constants and Values. There are only 2 of these. They must know their own shapes. @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Matrix): + def _convert_tree_to_intermediate(self, symbol: pybamm.Matrix): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() - if value.shape==(1,1): - self._intermediate[my_id] = JuliaScalar(my_id,value) + if value.shape == (1, 1): + self._intermediate[my_id] = JuliaScalar(my_id, value) else: - self._intermediate[my_id] = JuliaConstant(my_id,value) + self._intermediate[my_id] = JuliaConstant(my_id, value) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Vector): + def _convert_tree_to_intermediate(self, symbol: pybamm.Vector): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() - if value.shape==(1,1): - self._intermediate[my_id] = JuliaScalar(my_id,value) + if value.shape == (1, 1): + self._intermediate[my_id] = JuliaScalar(my_id, value) else: - self._intermediate[my_id] = JuliaConstant(my_id,value) + self._intermediate[my_id] = JuliaConstant(my_id, value) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Scalar): + def _convert_tree_to_intermediate(self, symbol: pybamm.Scalar): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() - self._intermediate[my_id] = JuliaScalar(my_id,value) + self._intermediate[my_id] = JuliaScalar(my_id, value) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.Time): + def _convert_tree_to_intermediate(self, symbol: pybamm.Time): my_id = symbol.id self._intermediate[my_id] = JuliaTime(my_id) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.InputParameter): + def _convert_tree_to_intermediate(self, symbol: pybamm.InputParameter): my_id = symbol.id name = symbol.name - self._intermediate[my_id] = JuliaInput(my_id,name) + self._intermediate[my_id] = JuliaInput(my_id, name) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.StateVector): + def _convert_tree_to_intermediate(self, symbol: pybamm.StateVector): my_id = symbol.id first_point = symbol.first_point last_point = symbol.last_point - points = (first_point,last_point) + points = (first_point, last_point) shape = symbol.shape - self._intermediate[my_id] = JuliaStateVector(id,points,shape) + self._intermediate[my_id] = JuliaStateVector(id, points, shape) return my_id - + @multimethod - def _convert_tree_to_intermediate(self,symbol:pybamm.StateVectorDot): + def _convert_tree_to_intermediate(self, symbol: pybamm.StateVectorDot): my_id = symbol.id first_point = symbol.first_point last_point = symbol.last_point - points = (first_point,last_point) + points = (first_point, last_point) shape = symbol.shape - self._intermediate[my_id] = JuliaStateVectorDot(id,points,shape) + self._intermediate[my_id] = JuliaStateVectorDot(id, points, shape) return my_id - - #convert intermediates to code. Again, all binary trees follow the same pattern so we just define a function to break them down, and then use the MD to find out what code to generate. - #Cache and Const Creation - def create_cache(self,symbol): + # Cache and Const Creation + def create_cache(self, symbol): my_id = symbol.output cache_shape = self._intermediate[my_id].shape cache_id = self._cache_id self._cache_id += 1 cache_name = "cache_{}".format(cache_id) if self._preallocate: - if self._cache_type=="standard": + if self._cache_type == "standard": if cache_shape[1] == 1: cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+="{} = zeros{}\n".format(cache_name,cache_shape_st) + self._cache_and_const_string += "{} = zeros{}\n".format( + cache_name, cache_shape_st + ) self._cache_dict[symbol.output] = cache_name - elif self._cache_type=="dual": + elif self._cache_type == "dual": if cache_shape[1] == 1: cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+="{}_init = dualcache(zeros{},12)\n".format(cache_name,cache_shape_st) - self._cache_initialization_string+=" {} = PreallocationTools.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_and_const_string += ( + "{}_init = dualcache(zeros{},12)\n".format( + cache_name, cache_shape_st + ) + ) + self._cache_initialization_string +=\ + "{} = PreallocationTools.get_tmp({}_init,(@view y[1:{}]))\n".format( + cache_name, cache_name, cache_shape[0] + ) self._cache_dict[symbol.output] = cache_name - elif self._cache_type=="symbolic": - if cache_shape[1]==1: + elif self._cache_type == "symbolic": + if cache_shape[1] == 1: cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+=" {}_init = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format(cache_name, cache_shape_st,cache_shape[0]) - self._cache_initialization_string+=" {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format(cache_name,cache_name,cache_shape[0]) + self._cache_and_const_string += ( + " {}_init = symcache(zeros{},Vector{{Num}}(undef,{}))\n".format( + cache_name, cache_shape_st, cache_shape[0] + ) + ) + self._cache_initialization_string += ( + " {} = PyBaMM.get_tmp({}_init,(@view y[1:{}]))\n".format( + cache_name, cache_name, cache_shape[0] + ) + ) self._cache_dict[symbol.output] = cache_name - elif self._cache_type=="gpu": - if cache_shape[1]==1: + elif self._cache_type == "gpu": + if cache_shape[1] == 1: cache_shape_st = "({})".format(cache_shape[0]) else: cache_shape_st = cache_shape - self._cache_and_const_string+="{} = CUDA.zeros{}\n".format(cache_name, cache_shape_st,cache_shape[0]) + self._cache_and_const_string += "{} = CUDA.zeros{}\n".format( + cache_name, cache_shape_st + ) self._cache_dict[symbol.output] = cache_name else: - raise NotImplementedError("The cache type you've specified has not yet been implemented") + raise NotImplementedError( + "The cache type you've specified has not yet been implemented" + ) return self._cache_dict[my_id] else: self._cache_dict[symbol.output] = cache_name return self._cache_dict[symbol.output] - - - def create_const(self,symbol): + def create_const(self, symbol): my_id = symbol.output - const_id = self._const_id+1 + const_id = self._const_id + 1 self._const_id = const_id const_name = "const_{}".format(const_id) self._const_dict[my_id] = const_name mat_value = symbol.value val_line = self.write_const(mat_value) - if self._cache_type=="gpu": - const_line = const_name+" = cu({})\n".format(val_line) + if self._cache_type == "gpu": + const_line = const_name + " = cu({})\n".format(val_line) else: - const_line = const_name+" = {}\n".format(val_line) - self._cache_and_const_string+=const_line + const_line = const_name + " = {}\n".format(val_line) + self._cache_and_const_string += const_line return 0 - + @multimethod - def write_const(self,mat_value:numpy.ndarray): + def write_const(self, mat_value: numpy.ndarray): return mat_value @multimethod - def write_const(self,value:scipy.sparse._csr.csr_matrix): + def write_const(self, value: scipy.sparse._csr.csr_matrix): row, col, data = scipy.sparse.find(value) m, n = value.shape np.set_printoptions( @@ -453,15 +521,15 @@ def write_const(self,value:scipy.sparse._csr.csr_matrix): ) val_string = "sparse({}, {}, {}{}, {}, {})".format( - np.array2string(row + 1, separator=","), - np.array2string(col + 1, separator=","), - self._type, - np.array2string(data, separator=","), - m, - n, - ) + np.array2string(row + 1, separator=","), + np.array2string(col + 1, separator=","), + self._type, + np.array2string(data, separator=","), + m, + n, + ) return val_string - + def clear(self): self._intermediate = OrderedDict() self._function_string = "" @@ -470,371 +538,492 @@ def clear(self): self._const_dict = OrderedDict() self._cache_id = 0 self._const_id = 0 - - #Just get something working here, so can start actual testing - def write_function_easy(self,funcname,inline=True): - #start with the closure + + # Just get something working here, so can start actual testing + def write_function_easy(self, funcname, inline=True): + # start with the closure top = self._intermediate[next(reversed(self._intermediate))] - #this line actually writes the code - top_var_name = top._convert_intermediate_to_code(self,inline=False) - #write the cache initialization - self._cache_and_const_string = "begin\n{} = let \n".format(funcname) + self._cache_and_const_string - self._cache_and_const_string = remove_lines_with(self._cache_and_const_string,top_var_name) - self._cache_initialization_string = remove_lines_with(self._cache_initialization_string,top_var_name) + # this line actually writes the code + top_var_name = top._convert_intermediate_to_code(self, inline=False) + # write the cache initialization + self._cache_and_const_string = ( + "begin\n{} = let \n".format(funcname) + self._cache_and_const_string + ) + self._cache_and_const_string = remove_lines_with( + self._cache_and_const_string, top_var_name + ) + self._cache_initialization_string = remove_lines_with( + self._cache_initialization_string, top_var_name + ) my_shape = top.shape if len(self.input_parameter_order) != 0: parameter_string = "" for parameter in self.input_parameter_order: - parameter_string+="{},".format(parameter) + parameter_string += "{},".format(parameter) parameter_string += "= p\n" self._function_string = parameter_string + self._function_string - self._function_string = self._cache_initialization_string + self._function_string + self._function_string = ( + self._cache_initialization_string + self._function_string + ) if self._preallocate: self._function_string += "\n return nothing\n" else: self._function_string += "\n return {}\n".format(top_var_name) if my_shape[1] != 1: - self._function_string = self._function_string.replace(top_var_name,"J") + self._function_string = self._function_string.replace(top_var_name, "J") self._function_string += "end\nend\nend" - self._function_string = "function {}(J, y, p, t)\n".format(funcname+"_with_consts") + self._function_string - elif self._dae_type=="semi-explicit": - self._function_string = self._function_string.replace(top_var_name,"dy") + self._function_string = ( + "function {}(J, y, p, t)\n".format(funcname + "_with_consts") + + self._function_string + ) + elif self._dae_type == "semi-explicit": + self._function_string = self._function_string.replace(top_var_name, "dy") self._function_string += "end\nend\nend" - self._function_string = "function {}(dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string - elif self._dae_type=="implicit": - self._function_string = self._function_string.replace(top_var_name,"out") + self._function_string = ( + "function {}(dy, y, p, t)\n".format(funcname + "_with_consts") + + self._function_string + ) + elif self._dae_type == "implicit": + self._function_string = self._function_string.replace(top_var_name, "out") self._function_string += "end\nend\nend" - self._function_string = "function {}(out, dy, y, p, t)\n".format(funcname+"_with_consts") + self._function_string + self._function_string = ( + "function {}(out, dy, y, p, t)\n".format(funcname + "_with_consts") + + self._function_string + ) return 0 - - #this function will be the top level. - def convert_tree_to_intermediate(self,symbol,len_rhs=None): + # this function will be the top level. + def convert_tree_to_intermediate(self, symbol, len_rhs=None): if self._dae_type == "implicit": - assert len_rhs != None + assert len_rhs is not None symbol_minus_dy = [] end = 0 for child in symbol.orphans: start = end end += child.size if end <= len_rhs: - symbol_minus_dy.append(child - pybamm.StateVectorDot(slice(start, end))) + symbol_minus_dy.append( + child - pybamm.StateVectorDot(slice(start, end)) + ) else: symbol_minus_dy.append(child) symbol = pybamm.numpy_concatenation(*symbol_minus_dy) self._convert_tree_to_intermediate(symbol) return 0 - #rework this at some point - def build_julia_code(self,funcname="f",inline=True): - #get top node of tree - self.write_function_easy(funcname,inline=inline) - string = self._cache_and_const_string+self._function_string + # rework this at some point + def build_julia_code(self, funcname="f", inline=True): + # get top node of tree + self.write_function_easy(funcname, inline=inline) + string = self._cache_and_const_string + self._function_string return string -#BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH +# BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH class JuliaBinaryOperation(object): - def __init__(self,left_input,right_input,output,shape): + def __init__(self, left_input, right_input, output, shape): self.left_input = left_input self.right_input = right_input self.output = output self.shape = shape - def get_binary_inputs(self,converter:JuliaConverter,inline=True): - left_input_var_name = converter._intermediate[self.left_input]._convert_intermediate_to_code(converter,inline=inline) - right_input_var_name = converter._intermediate[self.right_input]._convert_intermediate_to_code(converter,inline=inline) - return left_input_var_name,right_input_var_name -#MatMul and Inner Product are not really the same as the bitwisebinary operations. + def get_binary_inputs(self, converter: JuliaConverter, inline=True): + left_input_var_name = converter._intermediate[ + self.left_input + ]._convert_intermediate_to_code(converter, inline=inline) + right_input_var_name = converter._intermediate[ + self.right_input + ]._convert_intermediate_to_code(converter, inline=inline) + return left_input_var_name, right_input_var_name + + +# MatMul and Inner Product are not really the same as the bitwisebinary operations. class JuliaMatrixMultiplication(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape): + def __init__(self, left_input, right_input, output, shape): self.left_input = left_input self.right_input = right_input self.output = output self.shape = shape - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=False): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False): if converter.cache_exists(self.output): return converter._cache_dict[self.output] result_var_name = converter.create_cache(self) - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=False) + left_input_var_name, right_input_var_name = self.get_binary_inputs( + converter, inline=False + ) result_var_name = converter._cache_dict[self.output] if converter._preallocate: - code = "mul!({},{},{})\n".format(result_var_name,left_input_var_name,right_input_var_name) + code = "mul!({},{},{})\n".format( + result_var_name, left_input_var_name, right_input_var_name + ) else: - code = "{} = {} * {}\n".format(result_var_name,left_input_var_name,right_input_var_name) - converter._function_string+=code + code = "{} = {} * {}\n".format( + result_var_name, left_input_var_name, right_input_var_name + ) + converter._function_string += code return result_var_name -#Includes Addition, subtraction, multiplication, division, power, minimum, and maximum +# Includes Addition, subtraction, multiplication, division, power, minimum, and maximum class JuliaBitwiseBinaryOperation(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape,operator): + def __init__(self, left_input, right_input, output, shape, operator): self.left_input = left_input self.right_input = right_input self.output = output self.shape = shape self.operator = operator - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) + left_input_var_name, right_input_var_name = self.get_binary_inputs( + converter, inline=True + ) if converter._preallocate: - code = "@. {} = {} {} {}\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) + code = "@. {} = {} {} {}\n".format( + result_var_name, + left_input_var_name, + self.operator, + right_input_var_name, + ) else: - code = "{} = @. ( {} {} {})\n".format(result_var_name,left_input_var_name,self.operator,right_input_var_name) - converter._function_string+=code + code = "{} = @. ( {} {} {})\n".format( + result_var_name, + left_input_var_name, + self.operator, + right_input_var_name, + ) + converter._function_string += code elif inline: - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) - result_var_name = "({} {} {})".format(left_input_var_name,self.operator,right_input_var_name) + left_input_var_name, right_input_var_name = self.get_binary_inputs( + converter, inline=True + ) + result_var_name = "({} {} {})".format( + left_input_var_name, self.operator, right_input_var_name + ) return result_var_name -#PREALLOCATING STOPPED HERE + + +# PREALLOCATING STOPPED HERE class JuliaAddition(JuliaBitwiseBinaryOperation): pass + class JuliaSubtraction(JuliaBitwiseBinaryOperation): pass + class JuliaMultiplication(JuliaBitwiseBinaryOperation): pass + class JuliaDivision(JuliaBitwiseBinaryOperation): pass + class JuliaPower(JuliaBitwiseBinaryOperation): pass -#MinMax is special because it does both min and max. Could be folded into JuliaBitwiseBinaryOperation once I do that + +# MinMax is special because it does both min and max. +# Could be folded into JuliaBitwiseBinaryOperation once I do that class JuliaMinMax(JuliaBinaryOperation): - def __init__(self,left_input,right_input,output,shape,name): + def __init__(self, left_input, right_input, output, shape, name): self.left_input = left_input self.right_input = right_input self.output = output self.shape = shape self.name = name - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) + left_input_var_name, right_input_var_name = self.get_binary_inputs( + converter, inline=True + ) if converter._preallocate: - code = "@. {} = {}({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) + code = "@. {} = {}({},{})\n".format( + result_var_name, + self.name, + left_input_var_name, + right_input_var_name, + ) else: - code = "{} = {}.({},{})\n".format(result_var_name,self.name,left_input_var_name,right_input_var_name) - converter._function_string+=code + code = "{} = {}.({},{})\n".format( + result_var_name, + self.name, + left_input_var_name, + right_input_var_name, + ) + converter._function_string += code elif inline: - left_input_var_name,right_input_var_name = self.get_binary_inputs(converter,inline=True) - result_var_name = "{}({},{})".format(self.name,left_input_var_name,right_input_var_name) + left_input_var_name, right_input_var_name = self.get_binary_inputs( + converter, inline=True + ) + result_var_name = "{}({},{})".format( + self.name, left_input_var_name, right_input_var_name + ) return result_var_name -#FUNCTIONS -##All Functions Return the same number of arguments they take in, except for minimum and maximum. + +# FUNCTIONS +# All Functions Return the same number of arguments +# they take in, except for minimum and maximum. class JuliaFunction(object): pass + class JuliaBroadcastableFunction(JuliaFunction): - def __init__(self,name,input,output,shape): + def __init__(self, name, input, output, shape): self.name = name self.input = input self.output = output self.shape = shape - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + input_var_name = converter._intermediate[ + self.input + ]._convert_intermediate_to_code(converter, inline=True) if converter._preallocate: - code = "@. {} = {}({})\n".format(result_var_name,self.name,input_var_name) + code = "@. {} = {}({})\n".format( + result_var_name, self.name, input_var_name + ) else: - code = "{} = {}.({})\n".format(result_var_name,self.name,input_var_name) - converter._function_string+=code + code = "{} = {}.({})\n".format( + result_var_name, self.name, input_var_name + ) + converter._function_string += code else: - #assume an @. has already been issued - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) - result_var_name = "({}({}))".format(self.name,input_var_name) + # assume an @. has already been issued + input_var_name = converter._intermediate[ + self.input + ]._convert_intermediate_to_code(converter, inline=True) + result_var_name = "({}({}))".format(self.name, input_var_name) return result_var_name + class JuliaNegation(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] inline = inline & converter._inline - + if not inline: result_var_name = converter.create_cache(self) - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + input_var_name = converter._intermediate[ + self.input + ]._convert_intermediate_to_code(converter, inline=True) if converter._preallocate: - code = "@. {} = - {}\n".format(result_var_name,input_var_name) + code = "@. {} = - {}\n".format(result_var_name, input_var_name) else: - code = "{} = -{}\n".format(result_var_name,input_var_name) - converter._function_string+=code + code = "{} = -{}\n".format(result_var_name, input_var_name) + converter._function_string += code else: - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=True) + input_var_name = converter._intermediate[ + self.input + ]._convert_intermediate_to_code(converter, inline=True) result_var_name = "(- {})".format(input_var_name) return result_var_name + class JuliaMinimumMaximum(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] result_var_name = converter.create_cache(self) - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + input_var_name = converter._intermediate[ + self.input + ]._convert_intermediate_to_code(converter, inline=False) if converter._preallocate: - code = "{} .= {}({})\n".format(result_var_name,self.name,input_var_name) + code = "{} .= {}({})\n".format(result_var_name, self.name, input_var_name) else: - code = "{} = {}({})\n".format(result_var_name,self.name,input_var_name) - converter._function_string+=code + code = "{} = {}({})\n".format(result_var_name, self.name, input_var_name) + converter._function_string += code return result_var_name -#Index is a little weird, so it just sits on its own. +# Index is a little weird, so it just sits on its own. class JuliaIndex(object): - def __init__(self,input,output,index): + def __init__(self, input, output, index): self.input = input self.output = output self.index = index if type(index) is slice: - if index.step == None: - self.shape = ((index.stop)-(index.start),1) + if index.step is None: + self.shape = ((index.stop) - (index.start), 1) elif type(index.step) == int: - self.shape = (floor((index.stop-index.start)/index.step),1) + self.shape = (floor((index.stop - index.start) / index.step), 1) else: print(index.step) raise NotImplementedError("asldhfjwaes") elif type(index) is int: - self.shape = (1,1) + self.shape = (1, 1) else: raise NotImplementedError("index must be slice or int") - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] index = self.index inline = inline & converter._inline if inline: - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + input_var_name = converter._intermediate[ + self.input + ]._convert_intermediate_to_code(converter, inline=False) if type(index) is int: - return "{}[{}]".format(input_var_name,index+1) + return "{}[{}]".format(input_var_name, index + 1) elif type(index) is slice: if index.step is None: - return "(@view {}[{}:{}])".format(input_var_name,index.start+1,index.stop) + return "(@view {}[{}:{}])".format( + input_var_name, index.start + 1, index.stop + ) elif type(index.step) is int: - return "(@view {}[{}:{}:{}])".format(input_var_name,index.start+1,index.step,index.stop) + return "(@view {}[{}:{}:{}])".format( + input_var_name, index.start + 1, index.step, index.stop + ) else: raise NotImplementedError("Step has to be an integer.") else: raise NotImplementedError("Step must be a slice or an int") else: result_var_name = converter.create_cache(self) - input_var_name = converter._intermediate[self.input]._convert_intermediate_to_code(converter,inline=False) + input_var_name = converter._intermediate[ + self.input + ]._convert_intermediate_to_code(converter, inline=False) if type(index) is int: - code = "@. {} = {}[{}]".format(result_var_name,input_var_name,index+1) + code = "@. {} = {}[{}]".format( + result_var_name, input_var_name, index + 1 + ) elif type(index) is slice: if index.step is None: - code = "@. {} = (@view {}[{}:{}])".format(result_var_name,input_var_name,index.start+1,index.stop) + code = "@. {} = (@view {}[{}:{}])".format( + result_var_name, input_var_name, index.start + 1, index.stop + ) elif type(index.step) is int: - code = "@. {} = (@view {}[{}:{}:{}])".format(result_var_name,input_var_name,index.start+1,index.step,index.stop) + code = "@. {} = (@view {}[{}:{}:{}])".format( + result_var_name, + input_var_name, + index.start + 1, + index.step, + index.stop, + ) else: raise NotImplementedError("Step has to be an integer.") else: raise NotImplementedError("Step must be a slice or an int") - converter._function_string+=code + converter._function_string += code return result_var_name - - -#Values and Constants -- I will need to change this to inputs, due to t, y, and p. +# Values and Constants -- I will need to change this to inputs, due to t, y, and p. class JuliaValue(object): pass + class JuliaConstant(JuliaValue): - def __init__(self,id,value): + def __init__(self, id, value): self.output = id self.value = value self.shape = value.shape - def _convert_intermediate_to_code(self,converter,inline=True): + + def _convert_intermediate_to_code(self, converter, inline=True): converter.create_const(self) return converter._const_dict[self.output] + class JuliaStateVector(JuliaValue): - def __init__(self,id,loc,shape): + def __init__(self, id, loc, shape): self.output = id self.loc = loc self.shape = shape - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): - start = self.loc[0]+1 + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + start = self.loc[0] + 1 end = self.loc[1] - if start==end: + if start == end: return "(y[{}])".format(start) else: - return "(@view y[{}:{}])".format(start,end) + return "(@view y[{}:{}])".format(start, end) + class JuliaStateVectorDot(JuliaStateVector): - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): - start = self.loc[0]+1 + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + start = self.loc[0] + 1 end = self.loc[1] - if start==end: + if start == end: return "(dy[{}])".format(start) else: - return "(@view dy[{}:{}])".format(start,end) + return "(@view dy[{}:{}])".format(start, end) + class JuliaScalar(JuliaConstant): - def __init__(self,id,value): + def __init__(self, id, value): self.output = id self.value = float(value) - self.shape = (1,1) - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + self.shape = (1, 1) + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): return self.value + class JuliaTime(JuliaScalar): - def __init__(self,id): + def __init__(self, id): self.output = id - self.shape = (1,1) - def _convert_intermediate_to_code(self,converter,inline=True): + self.shape = (1, 1) + + def _convert_intermediate_to_code(self, converter, inline=True): return "t" + class JuliaInput(JuliaScalar): - def __init__(self,id,name): + def __init__(self, id, name): self.output = id - self.shape = (1,1) + self.shape = (1, 1) self.name = name - def _convert_intermediate_to_code(self,converter,inline=True): - return self.name + def _convert_intermediate_to_code(self, converter, inline=True): + return self.name -#CONCATENATIONS +# CONCATENATIONS class JuliaConcatenation(object): - def __init__(self,output,shape,children): + def __init__(self, output, shape, children): self.output = output self.shape = shape self.children = children - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] - input_var_names = [] num_cols = self.shape[1] my_name = converter.create_cache(self) - #assume we don't have tensors. Already asserted that concatenations have to have the same width. - if num_cols==1: + # assume we don't have tensors. Already asserted + # concatenations have to have the same width. + if num_cols == 1: right_parenthesis = "]" - vec=True + vec = True else: right_parenthesis = ",:]" - vec=False - - #do the 0th one outside of the loop to initialize + vec = False + + # do the 0th one outside of the loop to initialize child = self.children[0] child_var = converter._intermediate[child] - child_var_name = child_var._convert_intermediate_to_code(converter,inline=True) + child_var_name = child_var._convert_intermediate_to_code(converter, inline=True) start_row = 1 if child_var.shape[0] == 0: end_row = 1 @@ -843,118 +1032,167 @@ def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): end_row = 1 if converter._preallocate: if vec: - code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,start_row,right_parenthesis,child_var_name) + code = "@. {}[{}:{}{} = {}\n".format( + my_name, start_row, start_row, right_parenthesis, child_var_name + ) else: - code = "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code = "{}[{}{} = {}\n".format( + my_name, start_row, right_parenthesis, child_var_name + ) else: - code = "{} = vcat({}".format(my_name,child_var_name) + code = "{} = vcat({}".format(my_name, child_var_name) else: start_row = 1 end_row = child_var.shape[0] if converter._preallocate: - code = "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) + code = "@. {}[{}:{}{} = {}\n".format( + my_name, start_row, end_row, right_parenthesis, child_var_name + ) else: - code = "{} = vcat({}".format(my_name,child_var_name) - + code = "{} = vcat({}".format(my_name, child_var_name) + for child in self.children[1:]: child_var = converter._intermediate[child] - child_var_name = child_var._convert_intermediate_to_code(converter,inline=True) + child_var_name = child_var._convert_intermediate_to_code( + converter, inline=True + ) if child_var.shape[0] == 0: continue elif child_var.shape[0] == 1: - start_row = end_row+1 - end_row = start_row+1 + start_row = end_row + 1 + end_row = start_row + 1 if converter._preallocate: if vec: - code += "{}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) + code += "{}[{}{} = {}\n".format( + my_name, start_row, right_parenthesis, child_var_name + ) else: - code += "@. {}[{}{} = {}\n".format(my_name,start_row,right_parenthesis,child_var_name) - elif child==self.children[-1]: + code += "@. {}[{}{} = {}\n".format( + my_name, start_row, right_parenthesis, child_var_name + ) + elif child == self.children[-1]: code += ",{})\n".format(child_var_name) else: code += ",{}".format(child_var_name) else: - start_row = end_row+1 - end_row = start_row+child_var.shape[0]-1 - if converter._preallocate: - code += "@. {}[{}:{}{} = {}\n".format(my_name,start_row,end_row,right_parenthesis,child_var_name) - elif child==self.children[-1]: + start_row = end_row + 1 + end_row = start_row + child_var.shape[0] - 1 + if converter._preallocate: + code += "@. {}[{}:{}{} = {}\n".format( + my_name, start_row, end_row, right_parenthesis, child_var_name + ) + elif child == self.children[-1]: code += ",{})\n".format(child_var_name) else: code += ",{}".format(child_var_name) - - converter._function_string+=code + + converter._function_string += code return my_name + class JuliaNumpyConcatenation(JuliaConcatenation): pass -#NOTE: CURRENTLY THIS BEHAVES EXACTLY LIKE NUMPYCONCATENATION + +# NOTE: CURRENTLY THIS BEHAVES EXACTLY LIKE NUMPYCONCATENATION class JuliaSparseStack(JuliaConcatenation): pass + class JuliaDomainConcatenation(JuliaConcatenation): - def __init__(self,output,shape,children,secondary_dimension_npts,children_slices): + def __init__( + self, output, shape, children, secondary_dimension_npts, children_slices + ): self.output = output self.shape = shape self.children = children self.secondary_dimension_npts = secondary_dimension_npts self.children_slices = children_slices - def _convert_intermediate_to_code(self,converter:JuliaConverter,inline=True): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] - input_var_names = [] num_cols = self.shape[1] result_var_name = converter.create_cache(self) - #assume we don't have tensors. Already asserted that concatenations have to have the same width. - if num_cols==1: + # assume we don't have tensors. Already asserted + # that concatenations have to have the same width. + if num_cols == 1: right_parenthesis = "]" - vec=True else: right_parenthesis = ",:]" - vec=False - #do the 0th one outside of the loop to initialize + # do the 0th one outside of the loop to initialize end_row = 0 code = "" if self.secondary_dimension_npts == 1: for c in range(len(self.children)): child = converter._intermediate[self.children[c]] - child_var_name = child._convert_intermediate_to_code(converter,inline=True) + child_var_name = child._convert_intermediate_to_code( + converter, inline=True + ) this_slice = list(self.children_slices[c].values())[0][0] start = this_slice.start stop = this_slice.stop - start_row = end_row+1 - end_row = start_row+(stop-start)-1 + start_row = end_row + 1 + end_row = start_row + (stop - start) - 1 if converter._preallocate: - code += "@. {}[{}:{}{} = {}\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name) + code += "@. {}[{}:{}{} = {}\n".format( + result_var_name, + start_row, + end_row, + right_parenthesis, + child_var_name, + ) else: - if c==0: - code+="{} = vcat({}".format(result_var_name,child_var_name) - elif c==len(self.children)-1: - code+=",{})\n".format(child_var_name) + if c == 0: + code += "{} = vcat({}".format(result_var_name, child_var_name) + elif c == len(self.children) - 1: + code += ",{})\n".format(child_var_name) else: - code +=",{}".format(child_var_name) - + code += ",{}".format(child_var_name) + else: for i in range(self.secondary_dimension_npts): for c in range(len(self.children)): child = converter._intermediate[self.children[c]] - child_var_name = child._convert_intermediate_to_code(converter,inline=True) + child_var_name = child._convert_intermediate_to_code( + converter, inline=True + ) this_slice = list(self.children_slices[c].values())[0][i] start = this_slice.start stop = this_slice.stop - start_row = end_row+1 - end_row = start_row+(stop-start)-1 + start_row = end_row + 1 + end_row = start_row + (stop - start) - 1 if converter._preallocate: - code += "@. {}[{}:{}{} = (@view {}[{}:{}{})\n".format(result_var_name,start_row,end_row,right_parenthesis,child_var_name,start+1,stop,right_parenthesis) + code += "@. {}[{}:{}{} = (@view {}[{}:{}{})\n".format( + result_var_name, + start_row, + end_row, + right_parenthesis, + child_var_name, + start + 1, + stop, + right_parenthesis, + ) else: - if (c==0) & (i==0): - code+="{} = vcat((@view {}[{}:{}{})".format(result_var_name,child_var_name,start+1,stop,right_parenthesis) - elif (c==len(self.children)-1) & (i==self.secondary_dimension_npts-1): - code+=",(@view {}[{}:{}{}))\n".format(child_var_name,start+1,stop,right_parenthesis) + if (c == 0) & (i == 0): + code += "{} = vcat((@view {}[{}:{}{})".format( + result_var_name, + child_var_name, + start + 1, + stop, + right_parenthesis, + ) + elif (c == len(self.children) - 1) & ( + i == self.secondary_dimension_npts - 1 + ): + code += ",(@view {}[{}:{}{}))\n".format( + child_var_name, start + 1, stop, right_parenthesis + ) else: - code +=",(@view {}[{}:{}{})".format(child_var_name,start+1,stop,right_parenthesis) - - converter._function_string+=code + code += ",(@view {}[{}:{}{})".format( + child_var_name, start + 1, stop, right_parenthesis + ) + + converter._function_string += code return result_var_name diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index a49aea3bd0..32bd244a0e 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1139,7 +1139,12 @@ def generate_julia_diffeq( if self.algebraic == {}: # ODE model: form dy[] = ... - converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline,preallocate=preallocate) + converter = pybamm.JuliaConverter( + input_parameter_order=input_parameter_order, + cache_type=cache_type, + inline=inline, + preallocate=preallocate, + ) converter.convert_tree_to_intermediate(self.concatenated_rhs) eqn_str = converter.build_julia_code(funcname=name) else: @@ -1147,9 +1152,20 @@ def generate_julia_diffeq( len_rhs = None else: len_rhs = self.concatenated_rhs.size - - converter = pybamm.JuliaConverter(dae_type=dae_type,input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline,preallocate=preallocate) - converter.convert_tree_to_intermediate(pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic),len_rhs=len_rhs) + + converter = pybamm.JuliaConverter( + dae_type=dae_type, + input_parameter_order=input_parameter_order, + cache_type=cache_type, + inline=inline, + preallocate=preallocate, + ) + converter.convert_tree_to_intermediate( + pybamm.numpy_concatenation( + self.concatenated_rhs, self.concatenated_algebraic + ), + len_rhs=len_rhs, + ) eqn_str = converter.build_julia_code(funcname=name) if get_consistent_ics_solver is None or self.algebraic == {}: @@ -1161,12 +1177,19 @@ def generate_julia_diffeq( if generate_jacobian: size_state = self.concatenated_initial_conditions.size - state_vector = pybamm.StateVector(slice(0,size_state)) - expr = pybamm.numpy_concatenation(self.concatenated_rhs,self.concatenated_algebraic).jac(state_vector) - jac_converter = pybamm.JuliaConverter(input_parameter_order=input_parameter_order,cache_type=cache_type,inline=inline,preallocate=preallocate) + state_vector = pybamm.StateVector(slice(0, size_state)) + expr = pybamm.numpy_concatenation( + self.concatenated_rhs, self.concatenated_algebraic + ).jac(state_vector) + jac_converter = pybamm.JuliaConverter( + input_parameter_order=input_parameter_order, + cache_type=cache_type, + inline=inline, + preallocate=preallocate, + ) jac_converter.convert_tree_to_intermediate(expr) - jac_str = jac_converter.build_julia_code(funcname="jac_"+name) - return eqn_str,ics,jac_str + jac_str = jac_converter.build_julia_code(funcname="jac_" + name) + return eqn_str, ics, jac_str return eqn_str, ics diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index b6bf98547e..b030b6a17d 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -37,16 +37,21 @@ def evaluate_and_test_equal( p = np.array(list(inputs.values())) pybamm_eval = expr.evaluate(t=t_tests[0], y=y_tests[0], inputs=inputs) - for preallocate in [True,False]: - myconverter = pybamm.JuliaConverter(preallocate=preallocate,input_parameter_order=input_parameter_order) + for preallocate in [True, False]: + myconverter = pybamm.JuliaConverter( + preallocate=preallocate, input_parameter_order=input_parameter_order + ) myconverter.convert_tree_to_intermediate(expr) evaluator_str = myconverter.build_julia_code(funcname="f") try: Main.seval(evaluator_str) except JuliaError as e: - text_file = open("julia_evaluator_{}.jl".format(kwargs["funcname"]), "w") - text_file.write(evaluator_str) - text_file.close() + #text_file = open( + # "julia_evaluator_{}.jl".format(kwargs["funcname"]), "w" + #) + #text_file.write(evaluator_str) + #text_file.close() + raise e for t_test, y_test in zip(t_tests, y_tests): dy = np.zeros_like(pybamm_eval) @@ -67,9 +72,11 @@ def evaluate_and_test_equal( ) except AssertionError as e: # debugging - #print(Main.dy, y_test, p, t_test) - #print(evaluator_str) - text_file = open("julia_evaluator_{}.jl".format(kwargs["funcname"]), "w") + # print(Main.dy, y_test, p, t_test) + # print(evaluator_str) + text_file = open( + "julia_evaluator_{}.jl".format(kwargs["funcname"]), "w" + ) text_file.write(evaluator_str) text_file.close() raise e @@ -109,7 +116,7 @@ def test_evaluator_julia(self): self.evaluate_and_test_equal(expr, None, funcname="g2") # test a larger expression - expr = a * b + b + a ** 2 / b + 2 * a + b / 2 + 4 + expr = a * b + b + a**2 / b + 2 * a + b / 2 + 4 self.evaluate_and_test_equal(expr, y_tests) # test something with time @@ -239,7 +246,7 @@ def test_evaluator_julia_domain_concatenation(self): combined_submesh = mesh.combine_submeshes(*c.domain) nodes = combined_submesh.nodes - y_tests = [nodes ** 2 + 1, np.cos(nodes)] + y_tests = [nodes**2 + 1, np.cos(nodes)] # discretise and evaluate the variable disc.set_variable_slices([c_n, c_s, c_p]) @@ -272,7 +279,7 @@ def test_evaluator_julia_domain_concatenation_2D(self): nodes = np.linspace( 0, 1, combined_submesh.npts * mesh["current collector"].npts ) - y_tests = [nodes ** 2 + 1, np.cos(nodes)] + y_tests = [nodes**2 + 1, np.cos(nodes)] # discretise and evaluate the variable disc.set_variable_slices([c_n, c_s, c_p]) @@ -309,7 +316,7 @@ def test_evaluator_julia_discretised_operators(self): # test nodes = combined_submesh.nodes - y_tests = [nodes ** 2 + 1, np.cos(nodes)] + y_tests = [nodes**2 + 1, np.cos(nodes)] for i, expr in enumerate([grad_eqn_disc, div_eqn_disc]): self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=8) From b8ba1f9e1fe9cb6e36ed2a4e70b408ae54c6fc04 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Mon, 3 Oct 2022 10:18:01 -0400 Subject: [PATCH 101/163] fix jacobian bug --- .../operations/evaluate_julia.py | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 7b01ddb9eb..c56c69cc12 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -260,9 +260,25 @@ def _convert_tree_to_intermediate(self, symbol: pybamm.NotEqualHeaviside): def _convert_tree_to_intermediate(self, symbol: pybamm.Index): assert len(symbol.children) == 1 id_lower = self._convert_tree_to_intermediate(symbol.children[0]) + child_shape = self._intermediate[id_lower].shape + child_ncols = child_shape[1] + + my_id = symbol.id index = symbol.index - self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index) + if type(index) is slice: + if index.step is None: + shape = ((index.stop) - (index.start), child_ncols) + elif type(index.step) == int: + shape = (floor((index.stop - index.start) / index.step), child_ncols) + else: + raise NotImplementedError("index must be slice or int") + elif type(index) is int: + shape = (1, child_ncols) + else: + raise NotImplementedError("index must be slice or int") + + self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index, shape) return my_id def find_broadcastable_shape(self, id_left, id_right): @@ -857,28 +873,22 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): # Index is a little weird, so it just sits on its own. class JuliaIndex(object): - def __init__(self, input, output, index): + def __init__(self, input, output, index, shape): self.input = input self.output = output self.index = index - if type(index) is slice: - if index.step is None: - self.shape = ((index.stop) - (index.start), 1) - elif type(index.step) == int: - self.shape = (floor((index.stop - index.start) / index.step), 1) - else: - print(index.step) - raise NotImplementedError("asldhfjwaes") - elif type(index) is int: - self.shape = (1, 1) - else: - raise NotImplementedError("index must be slice or int") + self.shape = shape + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output): return converter._cache_dict[self.output] index = self.index inline = inline & converter._inline + if self.shape[1] == 1: + right_parenthesis = "]" + else: + right_parenthesis = ",:]" if inline: input_var_name = converter._intermediate[ self.input @@ -887,12 +897,12 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): return "{}[{}]".format(input_var_name, index + 1) elif type(index) is slice: if index.step is None: - return "(@view {}[{}:{}])".format( - input_var_name, index.start + 1, index.stop + return "(@view {}[{}:{}{})".format( + input_var_name, index.start + 1, index.stop, right_parenthesis ) elif type(index.step) is int: - return "(@view {}[{}:{}:{}])".format( - input_var_name, index.start + 1, index.step, index.stop + return "(@view {}[{}:{}:{}{})".format( + input_var_name, index.start + 1, index.step, index.stop, right_parenthesis ) else: raise NotImplementedError("Step has to be an integer.") @@ -904,21 +914,23 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): self.input ]._convert_intermediate_to_code(converter, inline=False) if type(index) is int: - code = "@. {} = {}[{}]".format( - result_var_name, input_var_name, index + 1 + code = "@. {} = {}[{}{}".format( + result_var_name, input_var_name, index + 1, right_parenthesis ) elif type(index) is slice: if index.step is None: - code = "@. {} = (@view {}[{}:{}])".format( - result_var_name, input_var_name, index.start + 1, index.stop + code = "@. {} = (@view {}[{}:{}{})".format( + result_var_name, input_var_name, index.start + 1, index.stop, right_parenthesis ) elif type(index.step) is int: - code = "@. {} = (@view {}[{}:{}:{}])".format( + code = "@. {} = (@view {}[{}:{}:{}{})".format( result_var_name, input_var_name, index.start + 1, index.step, index.stop, + right_parenthesis, + ) else: raise NotImplementedError("Step has to be an integer.") From 54ae313387a24739901e3bc81845a79c9327db7e Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Tue, 4 Oct 2022 04:44:48 -0400 Subject: [PATCH 102/163] parallelism --- .../operations/evaluate_julia.py | 332 ++++++++++++++---- 1 file changed, 257 insertions(+), 75 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c56c69cc12..b743b7af93 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -8,6 +8,19 @@ from collections import OrderedDict from multimethod import multimethod from math import floor +import re + +#Option 1: +#When save cache dict, do something +#Option 2: +#Find out a better way to store the data + + +def get_lower_keys(key,all_keys): + key_length = len(key) + all_lower_keys = list(filter(lambda this_key : len(this_key)>key_length, all_keys)) + my_lower_keys = list(filter(lambda this_key : this_key[0:key_length]==key, all_lower_keys)) + return my_lower_keys def is_constant_and_can_evaluate(symbol): @@ -45,6 +58,7 @@ def __init__( dae_type="semi-explicit", input_parameter_order=[], inline=True, + parallel=False, ): assert not ismtk @@ -57,6 +71,7 @@ def __init__( self._type = "Float64" self._inline = inline + self.parallel = parallel # "Caches" # Stores Constants to be Declared in the initial cache # insight: everything is just a line of code @@ -69,6 +84,8 @@ def __init__( # Variable Names to be used to generate the code. self._cache_dict = OrderedDict() self._const_dict = OrderedDict() + self.parallel_dict = {} + self.inverse_parallel_dict = {} self.input_parameter_order = input_parameter_order @@ -81,7 +98,16 @@ def __init__( self._return_string = "" self._cache_initialization_string = "" - def cache_exists(self, id): + def cache_exists(self, id, loc=""): + if self._cache_dict.get(id) is not None: + print("warning, cache duplicated for {}".format(self._cache_dict[id])) + if self.parallel: + old_loc = self.inverse_parallel_dict.get(id) + if old_loc is not None: + if len(old_loc)|\=|\)|\(|\,|\+|\@|\.|\ |\n" + if ("mul!" in this_line): + # var is the first thing after the + var_names = re.split(res_to_look_for, this_line) + var_names = list(filter(("").__ne__, var_names)) + this_var_name = var_names[1] + else: + var_names = re.split(res_to_look_for, this_line) + var_names = list(filter(("").__ne__, var_names)) + this_var_name = var_names[0] + for other_key in keys_from_other_top_levels: + other_line = self.parallel_dict[other_key] + other_line_var_names = re.split(res_to_look_for,other_line) + other_line_var_names = list(filter(("").__ne__, other_line_var_names)) + if this_var_name in other_line_var_names: + print("WARNING race condition found where {} is needed by {}".format(key, other_key)) + my_lower_keys = get_lower_keys(key,all_keys) + for lower_key in my_lower_keys: + if race_heuristic == "search_and_sync": + new_lower_key = " " + lower_key + self.parallel_dict[new_lower_key] = self.parallel_dict[lower_key] + if lower_key in all_keys: + all_keys.remove(lower_key) + self.parallel_dict[lower_key] = "#test\n" + elif race_heuristic == "recompute": + new_lower_key = other_key + lower_key + self.parallel_dict[new_lower_key] = self.parallel_dict[lower_key] + if race_heuristic == "search_and_sync": + new_key = " " + key + self.parallel_dict[new_key] = self.parallel_dict[key] + if key in all_keys: + all_keys.remove(key) + self.parallel_dict[key] = "#test\n" + elif race_heuristic == "recompute": + new_key = other_key + key + self.parallel_dict[new_key] = self.parallel_dict[key] + + # BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH class JuliaBinaryOperation(object): @@ -643,13 +788,13 @@ def __init__(self, left_input, right_input, output, shape): self.output = output self.shape = shape - def get_binary_inputs(self, converter: JuliaConverter, inline=True): + def get_binary_inputs(self, converter: JuliaConverter, inline=True, loc=""): left_input_var_name = converter._intermediate[ self.left_input - ]._convert_intermediate_to_code(converter, inline=inline) + ]._convert_intermediate_to_code(converter, inline=inline, loc = loc + "a") right_input_var_name = converter._intermediate[ self.right_input - ]._convert_intermediate_to_code(converter, inline=inline) + ]._convert_intermediate_to_code(converter, inline=inline, loc = loc + "b") return left_input_var_name, right_input_var_name @@ -661,12 +806,12 @@ def __init__(self, left_input, right_input, output, shape): self.output = output self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False,loc=""): + if converter.cache_exists(self.output, loc): return converter._cache_dict[self.output] result_var_name = converter.create_cache(self) left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=False + converter, inline=False, loc=loc ) result_var_name = converter._cache_dict[self.output] if converter._preallocate: @@ -677,7 +822,12 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False) code = "{} = {} * {}\n".format( result_var_name, left_input_var_name, right_input_var_name ) - converter._function_string += code + #mat-mul is always creating a cache + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code return result_var_name @@ -690,14 +840,14 @@ def __init__(self, left_input, right_input, output, shape, operator): self.shape = shape self.operator = operator - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True + converter, inline=True, loc=loc ) if converter._preallocate: code = "@. {} = {} {} {}\n".format( @@ -713,10 +863,14 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): self.operator, right_input_var_name, ) - converter._function_string += code + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code elif inline: left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True + converter, inline=True, loc=loc ) result_var_name = "({} {} {})".format( left_input_var_name, self.operator, right_input_var_name @@ -755,15 +909,15 @@ def __init__(self, left_input, right_input, output, shape, name): self.shape = shape self.name = name - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True + converter, inline=True, loc=loc ) if converter._preallocate: code = "@. {} = {}({},{})\n".format( @@ -779,10 +933,14 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): left_input_var_name, right_input_var_name, ) - converter._function_string += code + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code elif inline: left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True + converter, inline=True, loc=loc ) result_var_name = "{}({},{})".format( self.name, left_input_var_name, right_input_var_name @@ -804,15 +962,15 @@ def __init__(self, name, input, output, shape): self.output = output self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True) + ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") if converter._preallocate: code = "@. {} = {}({})\n".format( result_var_name, self.name, input_var_name @@ -821,19 +979,23 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): code = "{} = {}.({})\n".format( result_var_name, self.name, input_var_name ) - converter._function_string += code + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code else: # assume an @. has already been issued input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True) + ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") result_var_name = "({}({}))".format(self.name, input_var_name) return result_var_name class JuliaNegation(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] inline = inline & converter._inline @@ -841,33 +1003,41 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True) + ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") if converter._preallocate: code = "@. {} = - {}\n".format(result_var_name, input_var_name) else: code = "{} = -{}\n".format(result_var_name, input_var_name) - converter._function_string += code + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code else: input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True) + ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") result_var_name = "(- {})".format(input_var_name) return result_var_name class JuliaMinimumMaximum(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=False) + ]._convert_intermediate_to_code(converter, inline=False, loc=loc+"a") if converter._preallocate: code = "{} .= {}({})\n".format(result_var_name, self.name, input_var_name) else: code = "{} = {}({})\n".format(result_var_name, self.name, input_var_name) - converter._function_string += code + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code return result_var_name @@ -880,8 +1050,8 @@ def __init__(self, input, output, index, shape): self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] index = self.index inline = inline & converter._inline @@ -892,7 +1062,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if inline: input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=False) + ]._convert_intermediate_to_code(converter, inline=False, loc=loc+"a") if type(index) is int: return "{}[{}]".format(input_var_name, index + 1) elif type(index) is slice: @@ -912,18 +1082,18 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=False) + ]._convert_intermediate_to_code(converter, inline=False, loc=loc+"a") if type(index) is int: code = "@. {} = {}[{}{}".format( result_var_name, input_var_name, index + 1, right_parenthesis ) elif type(index) is slice: if index.step is None: - code = "@. {} = (@view {}[{}:{}{})".format( + code = "@. {} = (@view {}[{}:{}{})\n".format( result_var_name, input_var_name, index.start + 1, index.stop, right_parenthesis ) elif type(index.step) is int: - code = "@. {} = (@view {}[{}:{}:{}{})".format( + code = "@. {} = (@view {}[{}:{}:{}{})\n".format( result_var_name, input_var_name, index.start + 1, @@ -936,7 +1106,11 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): raise NotImplementedError("Step has to be an integer.") else: raise NotImplementedError("Step must be a slice or an int") - converter._function_string += code + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code return result_var_name @@ -951,7 +1125,7 @@ def __init__(self, id, value): self.value = value self.shape = value.shape - def _convert_intermediate_to_code(self, converter, inline=True): + def _convert_intermediate_to_code(self, converter, inline=True, loc=""): converter.create_const(self) return converter._const_dict[self.output] @@ -962,7 +1136,7 @@ def __init__(self, id, loc, shape): self.loc = loc self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): start = self.loc[0] + 1 end = self.loc[1] if start == end: @@ -972,7 +1146,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): class JuliaStateVectorDot(JuliaStateVector): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): start = self.loc[0] + 1 end = self.loc[1] if start == end: @@ -987,7 +1161,7 @@ def __init__(self, id, value): self.value = float(value) self.shape = (1, 1) - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): return self.value @@ -996,7 +1170,7 @@ def __init__(self, id): self.output = id self.shape = (1, 1) - def _convert_intermediate_to_code(self, converter, inline=True): + def _convert_intermediate_to_code(self, converter, inline=True, loc=""): return "t" @@ -1006,7 +1180,7 @@ def __init__(self, id, name): self.shape = (1, 1) self.name = name - def _convert_intermediate_to_code(self, converter, inline=True): + def _convert_intermediate_to_code(self, converter, inline=True, loc=""): return self.name @@ -1017,8 +1191,8 @@ def __init__(self, output, shape, children): self.shape = shape self.children = children - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] num_cols = self.shape[1] my_name = converter.create_cache(self) @@ -1035,7 +1209,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): # do the 0th one outside of the loop to initialize child = self.children[0] child_var = converter._intermediate[child] - child_var_name = child_var._convert_intermediate_to_code(converter, inline=True) + child_var_name = child_var._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") start_row = 1 if child_var.shape[0] == 0: end_row = 1 @@ -1048,7 +1222,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): my_name, start_row, start_row, right_parenthesis, child_var_name ) else: - code = "{}[{}{} = {}\n".format( + code = " {}[{}{} = {}\n".format( my_name, start_row, right_parenthesis, child_var_name ) else: @@ -1061,13 +1235,14 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): my_name, start_row, end_row, right_parenthesis, child_var_name ) else: - code = "{} = vcat({}".format(my_name, child_var_name) - + code = " {} = vcat( {} ".format(my_name, child_var_name) + counter = "b" for child in self.children[1:]: child_var = converter._intermediate[child] child_var_name = child_var._convert_intermediate_to_code( - converter, inline=True + converter, inline=True, loc=loc+counter ) + counter = chr(ord(counter) + 1) if child_var.shape[0] == 0: continue elif child_var.shape[0] == 1: @@ -1075,30 +1250,33 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): end_row = start_row + 1 if converter._preallocate: if vec: - code += "{}[{}{} = {}\n".format( + code += "{}[{}{} = {} \n".format( my_name, start_row, right_parenthesis, child_var_name ) else: - code += "@. {}[{}{} = {}\n".format( + code += "@. {}[{}{} = {} \n".format( my_name, start_row, right_parenthesis, child_var_name ) elif child == self.children[-1]: - code += ",{})\n".format(child_var_name) + code += ",{} )\n".format(child_var_name) else: - code += ",{}".format(child_var_name) + code += ", {} ".format(child_var_name) else: start_row = end_row + 1 end_row = start_row + child_var.shape[0] - 1 if converter._preallocate: - code += "@. {}[{}:{}{} = {}\n".format( + code += "@. {}[{}:{}{} = {} \n".format( my_name, start_row, end_row, right_parenthesis, child_var_name ) elif child == self.children[-1]: - code += ",{})\n".format(child_var_name) + code += ",{} )\n".format(child_var_name) else: - code += ",{}".format(child_var_name) - - converter._function_string += code + code += ", {} ".format(child_var_name) + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code return my_name @@ -1121,8 +1299,8 @@ def __init__( self.secondary_dimension_npts = secondary_dimension_npts self.children_slices = children_slices - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): - if converter.cache_exists(self.output): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + if converter.cache_exists(self.output,loc): return converter._cache_dict[self.output] num_cols = self.shape[1] result_var_name = converter.create_cache(self) @@ -1140,7 +1318,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): for c in range(len(self.children)): child = converter._intermediate[self.children[c]] child_var_name = child._convert_intermediate_to_code( - converter, inline=True + converter, inline=True, loc = loc+chr(ord("a")+c) ) this_slice = list(self.children_slices[c].values())[0][0] start = this_slice.start @@ -1148,7 +1326,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): start_row = end_row + 1 end_row = start_row + (stop - start) - 1 if converter._preallocate: - code += "@. {}[{}:{}{} = {}\n".format( + code += "@. {}[{}:{}{} = {} \n".format( result_var_name, start_row, end_row, @@ -1157,18 +1335,19 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): ) else: if c == 0: - code += "{} = vcat({}".format(result_var_name, child_var_name) + code += "{} = vcat( {} ".format(result_var_name, child_var_name) elif c == len(self.children) - 1: - code += ",{})\n".format(child_var_name) + code += ", {} )\n".format(child_var_name) else: - code += ",{}".format(child_var_name) + code += ", {}".format(child_var_name) else: + num_chil = len(self.children) for i in range(self.secondary_dimension_npts): - for c in range(len(self.children)): + for c in range(num_chil): child = converter._intermediate[self.children[c]] child_var_name = child._convert_intermediate_to_code( - converter, inline=True + converter, inline=True, loc = loc+chr(ord("a")+(i*num_chil+c)) ) this_slice = list(self.children_slices[c].values())[0][i] start = this_slice.start @@ -1205,6 +1384,9 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): code += ",(@view {}[{}:{}{})".format( child_var_name, start + 1, stop, right_parenthesis ) - - converter._function_string += code + if converter.parallel: + converter.parallel_dict[loc] = code + converter.inverse_parallel_dict[self.output] = loc + else: + converter._function_string += code return result_var_name From 9972b8ecc273f2389f4b9ec066d00ee2c5398a99 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Tue, 4 Oct 2022 04:55:32 -0400 Subject: [PATCH 103/163] typo --- pybamm/expression_tree/operations/evaluate_julia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index b743b7af93..a3bda49e10 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -589,7 +589,7 @@ def write_function_easy(self, funcname, inline=True, topcut_options={"race heuri # this line actually writes the code top_var_name = top._convert_intermediate_to_code(self, inline=False) #if parallel is true, we haven't actually written the function yet - alg = "top_cut" + alg = "level_sync" if self.parallel: if alg == "top_cut": self.top_cut(topcut_options) From 884af37272c8cb352ea501e73698fab7927e7907 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Tue, 4 Oct 2022 12:51:06 -0400 Subject: [PATCH 104/163] asdfjhasdfasd --- .../operations/evaluate_julia.py | 23 +- test_parallel.jl | 1466 +++++++++++++++++ test_parallel.py | 34 + 3 files changed, 1515 insertions(+), 8 deletions(-) create mode 100644 test_parallel.jl create mode 100644 test_parallel.py diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index a3bda49e10..f9be99fb9e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -99,15 +99,22 @@ def __init__( self._cache_initialization_string = "" def cache_exists(self, id, loc=""): + if self._cache_dict.get(id)=="cache_28": + print("cache 28 is {}".format(self._intermediate[id])) + elif self._cache_dict.get(id) =="cache_24": + print("cache 24 is {}".format(self._intermediate[id])) if self._cache_dict.get(id) is not None: - print("warning, cache duplicated for {}".format(self._cache_dict[id])) - if self.parallel: - old_loc = self.inverse_parallel_dict.get(id) - if old_loc is not None: - if len(old_loc) Date: Fri, 7 Oct 2022 11:32:02 -0400 Subject: [PATCH 105/163] dag --- .../operations/evaluate_julia.py | 411 +++++++----------- .../test_operations/test_evaluate_julia.py | 4 +- 2 files changed, 159 insertions(+), 256 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index f9be99fb9e..55f1819503 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -9,11 +9,7 @@ from multimethod import multimethod from math import floor import re - -#Option 1: -#When save cache dict, do something -#Option 2: -#Find out a better way to store the data +import graphlib def get_lower_keys(key,all_keys): @@ -29,13 +25,16 @@ def is_constant_and_can_evaluate(symbol): Returns False otherwise. An example of a constant symbol that cannot be "evaluated" is PrimaryBroadcast(0). """ - if symbol.is_constant(): - try: - symbol.evaluate() - return True - except NotImplementedError: + try: + if symbol.is_constant(): + try: + symbol.evaluate() + return True + except NotImplementedError: + return False + else: return False - else: + except: return False @@ -58,10 +57,13 @@ def __init__( dae_type="semi-explicit", input_parameter_order=[], inline=True, - parallel=False, + parallel="legacy-serial" ): assert not ismtk + if parallel != "legacy-serial" and inline: + raise NotImplementedError("Inline not supported with anything other than legacy-serial") + # Characteristics self._cache_type = cache_type self._ismtk = ismtk @@ -71,7 +73,7 @@ def __init__( self._type = "Float64" self._inline = inline - self.parallel = parallel + self._parallel = parallel # "Caches" # Stores Constants to be Declared in the initial cache # insight: everything is just a line of code @@ -84,8 +86,10 @@ def __init__( # Variable Names to be used to generate the code. self._cache_dict = OrderedDict() self._const_dict = OrderedDict() - self.parallel_dict = {} - self.inverse_parallel_dict = {} + + #the real hero + self._dag = {} + self._code = {} self.input_parameter_order = input_parameter_order @@ -98,24 +102,12 @@ def __init__( self._return_string = "" self._cache_initialization_string = "" - def cache_exists(self, id, loc=""): - if self._cache_dict.get(id)=="cache_28": - print("cache 28 is {}".format(self._intermediate[id])) - elif self._cache_dict.get(id) =="cache_24": - print("cache 24 is {}".format(self._intermediate[id])) - if self._cache_dict.get(id) is not None: - if self.parallel: - old_loc = self.inverse_parallel_dict.get(id) - if old_loc is not None: - if len(old_loc)|\=|\)|\(|\,|\+|\@|\.|\ |\n" - if ("mul!" in this_line): - # var is the first thing after the - var_names = re.split(res_to_look_for, this_line) - var_names = list(filter(("").__ne__, var_names)) - this_var_name = var_names[1] - else: - var_names = re.split(res_to_look_for, this_line) - var_names = list(filter(("").__ne__, var_names)) - this_var_name = var_names[0] - for other_key in keys_from_other_top_levels: - other_line = self.parallel_dict[other_key] - other_line_var_names = re.split(res_to_look_for,other_line) - other_line_var_names = list(filter(("").__ne__, other_line_var_names)) - if this_var_name in other_line_var_names: - print("WARNING race condition found where {} is needed by {}".format(key, other_key)) - my_lower_keys = get_lower_keys(key,all_keys) - for lower_key in my_lower_keys: - if race_heuristic == "search_and_sync": - new_lower_key = " " + lower_key - self.parallel_dict[new_lower_key] = self.parallel_dict[lower_key] - if lower_key in all_keys: - all_keys.remove(lower_key) - self.parallel_dict[lower_key] = "#test\n" - elif race_heuristic == "recompute": - new_lower_key = other_key + lower_key - self.parallel_dict[new_lower_key] = self.parallel_dict[lower_key] - if race_heuristic == "search_and_sync": - new_key = " " + key - self.parallel_dict[new_key] = self.parallel_dict[key] - if key in all_keys: - all_keys.remove(key) - self.parallel_dict[key] = "#test\n" - elif race_heuristic == "recompute": - new_key = other_key + key - self.parallel_dict[new_key] = self.parallel_dict[key] - - # BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH class JuliaBinaryOperation(object): @@ -795,14 +697,22 @@ def __init__(self, left_input, right_input, output, shape): self.output = output self.shape = shape - def get_binary_inputs(self, converter: JuliaConverter, inline=True, loc=""): + def get_binary_inputs(self, converter: JuliaConverter, inline=True): left_input_var_name = converter._intermediate[ self.left_input - ]._convert_intermediate_to_code(converter, inline=inline, loc = loc + "a") + ]._convert_intermediate_to_code(converter, inline=inline) right_input_var_name = converter._intermediate[ self.right_input - ]._convert_intermediate_to_code(converter, inline=inline, loc = loc + "b") + ]._convert_intermediate_to_code(converter, inline=inline) return left_input_var_name, right_input_var_name + + def generate_code_and_dag(self, converter, code): + converter._code[self.output] = code + l_id = converter._intermediate[self.left_input].output + r_id = converter._intermediate[self.right_input].output + converter._dag[self.output] = {l_id, r_id} + if converter._parallel == "legacy-serial": + converter._function_string += code # MatMul and Inner Product are not really the same as the bitwisebinary operations. @@ -813,12 +723,12 @@ def __init__(self, left_input, right_input, output, shape): self.output = output self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False,loc=""): - if converter.cache_exists(self.output, loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False): + if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] result_var_name = converter.create_cache(self) left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=False, loc=loc + converter, inline=False ) result_var_name = converter._cache_dict[self.output] if converter._preallocate: @@ -830,11 +740,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False, result_var_name, left_input_var_name, right_input_var_name ) #mat-mul is always creating a cache - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) return result_var_name @@ -847,14 +753,14 @@ def __init__(self, left_input, right_input, output, shape, operator): self.shape = shape self.operator = operator - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True, loc=loc + converter, inline=True ) if converter._preallocate: code = "@. {} = {} {} {}\n".format( @@ -870,14 +776,10 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, self.operator, right_input_var_name, ) - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) elif inline: left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True, loc=loc + converter, inline=True ) result_var_name = "({} {} {})".format( left_input_var_name, self.operator, right_input_var_name @@ -916,15 +818,15 @@ def __init__(self, left_input, right_input, output, shape, name): self.shape = shape self.name = name - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True, loc=loc + converter, inline=True ) if converter._preallocate: code = "@. {} = {}({},{})\n".format( @@ -940,14 +842,10 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, left_input_var_name, right_input_var_name, ) - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) elif inline: left_input_var_name, right_input_var_name = self.get_binary_inputs( - converter, inline=True, loc=loc + converter, inline=True ) result_var_name = "{}({},{})".format( self.name, left_input_var_name, right_input_var_name @@ -968,16 +866,22 @@ def __init__(self, name, input, output, shape): self.input = input self.output = output self.shape = shape + + def generate_code_and_dag(self, converter: JuliaConverter, code): + converter._code[self.output] = code + converter._dag[self.output] = {converter._intermediate[self.input].output} + if converter._parallel == "legacy-serial": + converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") + ]._convert_intermediate_to_code(converter, inline=True) if converter._preallocate: code = "@. {} = {}({})\n".format( result_var_name, self.name, input_var_name @@ -986,23 +890,19 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, code = "{} = {}.({})\n".format( result_var_name, self.name, input_var_name ) - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) else: # assume an @. has already been issued input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") + ]._convert_intermediate_to_code(converter, inline=True) result_var_name = "({}({}))".format(self.name, input_var_name) return result_var_name class JuliaNegation(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] inline = inline & converter._inline @@ -1010,41 +910,33 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") + ]._convert_intermediate_to_code(converter, inline=True) if converter._preallocate: code = "@. {} = - {}\n".format(result_var_name, input_var_name) else: code = "{} = -{}\n".format(result_var_name, input_var_name) - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) else: input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") + ]._convert_intermediate_to_code(converter, inline=True) result_var_name = "(- {})".format(input_var_name) return result_var_name class JuliaMinimumMaximum(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=False, loc=loc+"a") + ]._convert_intermediate_to_code(converter, inline=False) if converter._preallocate: code = "{} .= {}({})\n".format(result_var_name, self.name, input_var_name) else: code = "{} = {}({})\n".format(result_var_name, self.name, input_var_name) - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) return result_var_name @@ -1055,10 +947,17 @@ def __init__(self, input, output, index, shape): self.output = output self.index = index self.shape = shape + + def generate_code_and_dag(self, converter: JuliaConverter, code): + input_id = converter._intermediate[self.input].output + converter._code[self.output] = code + converter._dag[self.output] = {input_id} + if converter._parallel == "legacy-serial": + converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] index = self.index inline = inline & converter._inline @@ -1069,7 +968,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, if inline: input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=False, loc=loc+"a") + ]._convert_intermediate_to_code(converter, inline=False) if type(index) is int: return "{}[{}]".format(input_var_name, index + 1) elif type(index) is slice: @@ -1089,7 +988,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, result_var_name = converter.create_cache(self) input_var_name = converter._intermediate[ self.input - ]._convert_intermediate_to_code(converter, inline=False, loc=loc+"a") + ]._convert_intermediate_to_code(converter, inline=False) if type(index) is int: code = "@. {} = {}[{}{}".format( result_var_name, input_var_name, index + 1, right_parenthesis @@ -1113,39 +1012,39 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, raise NotImplementedError("Step has to be an integer.") else: raise NotImplementedError("Step must be a slice or an int") - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) return result_var_name # Values and Constants -- I will need to change this to inputs, due to t, y, and p. class JuliaValue(object): - pass + def generate_code_and_dag(self, converter: JuliaConverter): + pass class JuliaConstant(JuliaValue): - def __init__(self, id, value): - self.output = id + def __init__(self, my_id, value): + self.output = my_id self.value = value self.shape = value.shape - def _convert_intermediate_to_code(self, converter, inline=True, loc=""): + def _convert_intermediate_to_code(self, converter, inline=True): converter.create_const(self) + self.generate_code_and_dag(converter) return converter._const_dict[self.output] + class JuliaStateVector(JuliaValue): - def __init__(self, id, loc, shape): - self.output = id + def __init__(self, my_id, loc, shape): + self.output = my_id self.loc = loc self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): start = self.loc[0] + 1 end = self.loc[1] + self.generate_code_and_dag(converter) if start == end: return "(y[{}])".format(start) else: @@ -1153,9 +1052,10 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, class JuliaStateVectorDot(JuliaStateVector): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): start = self.loc[0] + 1 end = self.loc[1] + self.generate_code_and_dag(converter) if start == end: return "(dy[{}])".format(start) else: @@ -1163,31 +1063,34 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, class JuliaScalar(JuliaConstant): - def __init__(self, id, value): - self.output = id + def __init__(self, my_id, value): + self.output = my_id self.value = float(value) self.shape = (1, 1) - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + self.generate_code_and_dag(converter) return self.value class JuliaTime(JuliaScalar): - def __init__(self, id): - self.output = id + def __init__(self, my_id): + self.output = my_id self.shape = (1, 1) - def _convert_intermediate_to_code(self, converter, inline=True, loc=""): + def _convert_intermediate_to_code(self, converter, inline=True): + self.generate_code_and_dag(converter) return "t" class JuliaInput(JuliaScalar): - def __init__(self, id, name): - self.output = id + def __init__(self, my_id, name): + self.output = my_id self.shape = (1, 1) self.name = name - def _convert_intermediate_to_code(self, converter, inline=True, loc=""): + def _convert_intermediate_to_code(self, converter, inline=True): + self.generate_code_and_dag(converter) return self.name @@ -1197,9 +1100,17 @@ def __init__(self, output, shape, children): self.output = output self.shape = shape self.children = children + + def generate_code_and_dag(self, converter: JuliaConverter,code): + ids = set(converter._intermediate[child].output for child in self.children) + converter._dag[self.output] = ids + converter._code[self.output] = code + if converter._parallel == "legacy-serial": + converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, self.children): return converter._cache_dict[self.output] num_cols = self.shape[1] my_name = converter.create_cache(self) @@ -1216,7 +1127,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, # do the 0th one outside of the loop to initialize child = self.children[0] child_var = converter._intermediate[child] - child_var_name = child_var._convert_intermediate_to_code(converter, inline=True, loc=loc+"a") + child_var_name = child_var._convert_intermediate_to_code(converter, inline=True) start_row = 1 if child_var.shape[0] == 0: end_row = 1 @@ -1247,7 +1158,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, for child in self.children[1:]: child_var = converter._intermediate[child] child_var_name = child_var._convert_intermediate_to_code( - converter, inline=True, loc=loc+counter + converter, inline=True ) counter = chr(ord(counter) + 1) if child_var.shape[0] == 0: @@ -1279,11 +1190,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, code += ",{} )\n".format(child_var_name) else: code += ", {} ".format(child_var_name) - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter,code) return my_name @@ -1306,8 +1213,8 @@ def __init__( self.secondary_dimension_npts = secondary_dimension_npts self.children_slices = children_slices - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, loc=""): - if converter.cache_exists(self.output,loc): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + if converter.cache_exists(self.output, self.children): return converter._cache_dict[self.output] num_cols = self.shape[1] result_var_name = converter.create_cache(self) @@ -1325,7 +1232,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, for c in range(len(self.children)): child = converter._intermediate[self.children[c]] child_var_name = child._convert_intermediate_to_code( - converter, inline=True, loc = loc+chr(ord("a")+c) + converter, inline=True ) this_slice = list(self.children_slices[c].values())[0][0] start = this_slice.start @@ -1354,7 +1261,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, for c in range(num_chil): child = converter._intermediate[self.children[c]] child_var_name = child._convert_intermediate_to_code( - converter, inline=True, loc = loc+chr(ord("a")+(i*num_chil+c)) + converter, inline=True ) this_slice = list(self.children_slices[c].values())[0][i] start = this_slice.start @@ -1391,9 +1298,5 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, code += ",(@view {}[{}:{}{})".format( child_var_name, start + 1, stop, right_parenthesis ) - if converter.parallel: - converter.parallel_dict[loc] = code - converter.inverse_parallel_dict[self.output] = loc - else: - converter._function_string += code + self.generate_code_and_dag(converter, code) return result_var_name diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index b030b6a17d..ef643b971e 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -319,7 +319,7 @@ def test_evaluator_julia_discretised_operators(self): y_tests = [nodes**2 + 1, np.cos(nodes)] for i, expr in enumerate([grad_eqn_disc, div_eqn_disc]): - self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=8) + self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}_asf", decimal=8) def test_evaluator_julia_discretised_microscale(self): # create discretisation @@ -364,7 +364,7 @@ def test_evaluator_julia_discretised_microscale(self): y_tests = [np.linspace(0, 1, total_npts) ** 2] for i, expr in enumerate([grad_eqn_disc, div_eqn_disc]): - self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=8) + self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}_asdf", decimal=8) if __name__ == "__main__": From bde8b2410fe0837610eac0569f43dbabf7a6a7df Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Fri, 7 Oct 2022 12:08:29 -0400 Subject: [PATCH 106/163] delete old;format --- .../operations/evaluate_julia.py | 57 +- .../operations/evaluate_julia_old.py | 1142 ----------------- 2 files changed, 32 insertions(+), 1167 deletions(-) delete mode 100644 pybamm/expression_tree/operations/evaluate_julia_old.py diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 55f1819503..631b7b6ef1 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -12,10 +12,12 @@ import graphlib -def get_lower_keys(key,all_keys): +def get_lower_keys(key, all_keys): key_length = len(key) - all_lower_keys = list(filter(lambda this_key : len(this_key)>key_length, all_keys)) - my_lower_keys = list(filter(lambda this_key : this_key[0:key_length]==key, all_lower_keys)) + all_lower_keys = list(filter(lambda this_key: len(this_key) > key_length, all_keys)) + my_lower_keys = list( + filter(lambda this_key: this_key[0:key_length] == key, all_lower_keys) + ) return my_lower_keys @@ -57,12 +59,14 @@ def __init__( dae_type="semi-explicit", input_parameter_order=[], inline=True, - parallel="legacy-serial" + parallel="legacy-serial", ): assert not ismtk if parallel != "legacy-serial" and inline: - raise NotImplementedError("Inline not supported with anything other than legacy-serial") + raise NotImplementedError( + "Inline not supported with anything other than legacy-serial" + ) # Characteristics self._cache_type = cache_type @@ -87,7 +91,7 @@ def __init__( self._cache_dict = OrderedDict() self._const_dict = OrderedDict() - #the real hero + # the real hero self._dag = {} self._code = {} @@ -288,7 +292,6 @@ def _convert_tree_to_intermediate(self, symbol: pybamm.Index): child_shape = self._intermediate[id_lower].shape child_ncols = child_shape[1] - my_id = symbol.id index = symbol.index if type(index) is slice: @@ -302,7 +305,7 @@ def _convert_tree_to_intermediate(self, symbol: pybamm.Index): shape = (1, child_ncols) else: raise NotImplementedError("index must be slice or int") - + self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index, shape) return my_id @@ -495,10 +498,11 @@ def create_cache(self, symbol): cache_name, cache_shape_st ) ) - self._cache_initialization_string +=\ + self._cache_initialization_string += ( "{} = PreallocationTools.get_tmp({}_init,(@view y[1:{}]))\n".format( cache_name, cache_name, cache_shape[0] ) + ) self._cache_dict[symbol.output] = cache_name elif self._cache_type == "symbolic": if cache_shape[1] == 1: @@ -581,7 +585,7 @@ def clear(self): self._code = {} self._cache_id = 0 self._const_id = 0 - + def write_function(self): ts = graphlib.TopologicalSorter(self._dag) if self._parallel is None: @@ -612,7 +616,7 @@ def write_function_easy(self, funcname, inline=True): top = self._intermediate[next(reversed(self._intermediate))] # this line actually writes the code top_var_name = top._convert_intermediate_to_code(self, inline=False) - #if parallel is true, we haven't actually written the function yet + # if parallel is true, we haven't actually written the function yet self.write_function() # write the cache initialization self._cache_and_const_string = ( @@ -680,7 +684,6 @@ def convert_tree_to_intermediate(self, symbol, len_rhs=None): self._convert_tree_to_intermediate(symbol) return 0 - # rework this at some point def build_julia_code(self, funcname="f", inline=True): # get top node of tree @@ -705,7 +708,7 @@ def get_binary_inputs(self, converter: JuliaConverter, inline=True): self.right_input ]._convert_intermediate_to_code(converter, inline=inline) return left_input_var_name, right_input_var_name - + def generate_code_and_dag(self, converter, code): converter._code[self.output] = code l_id = converter._intermediate[self.left_input].output @@ -739,7 +742,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False) code = "{} = {} * {}\n".format( result_var_name, left_input_var_name, right_input_var_name ) - #mat-mul is always creating a cache + # mat-mul is always creating a cache self.generate_code_and_dag(converter, code) return result_var_name @@ -866,7 +869,7 @@ def __init__(self, name, input, output, shape): self.input = input self.output = output self.shape = shape - + def generate_code_and_dag(self, converter: JuliaConverter, code): converter._code[self.output] = code converter._dag[self.output] = {converter._intermediate[self.input].output} @@ -947,7 +950,7 @@ def __init__(self, input, output, index, shape): self.output = output self.index = index self.shape = shape - + def generate_code_and_dag(self, converter: JuliaConverter, code): input_id = converter._intermediate[self.input].output converter._code[self.output] = code @@ -955,7 +958,6 @@ def generate_code_and_dag(self, converter: JuliaConverter, code): if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] @@ -978,7 +980,11 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): ) elif type(index.step) is int: return "(@view {}[{}:{}:{}{})".format( - input_var_name, index.start + 1, index.step, index.stop, right_parenthesis + input_var_name, + index.start + 1, + index.step, + index.stop, + right_parenthesis, ) else: raise NotImplementedError("Step has to be an integer.") @@ -996,7 +1002,11 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): elif type(index) is slice: if index.step is None: code = "@. {} = (@view {}[{}:{}{})\n".format( - result_var_name, input_var_name, index.start + 1, index.stop, right_parenthesis + result_var_name, + input_var_name, + index.start + 1, + index.stop, + right_parenthesis, ) elif type(index.step) is int: code = "@. {} = (@view {}[{}:{}:{}{})\n".format( @@ -1006,7 +1016,6 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): index.step, index.stop, right_parenthesis, - ) else: raise NotImplementedError("Step has to be an integer.") @@ -1034,7 +1043,6 @@ def _convert_intermediate_to_code(self, converter, inline=True): return converter._const_dict[self.output] - class JuliaStateVector(JuliaValue): def __init__(self, my_id, loc, shape): self.output = my_id @@ -1100,15 +1108,14 @@ def __init__(self, output, shape, children): self.output = output self.shape = shape self.children = children - - def generate_code_and_dag(self, converter: JuliaConverter,code): + + def generate_code_and_dag(self, converter: JuliaConverter, code): ids = set(converter._intermediate[child].output for child in self.children) converter._dag[self.output] = ids converter._code[self.output] = code if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): if converter.cache_exists(self.output, self.children): return converter._cache_dict[self.output] @@ -1190,7 +1197,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): code += ",{} )\n".format(child_var_name) else: code += ", {} ".format(child_var_name) - self.generate_code_and_dag(converter,code) + self.generate_code_and_dag(converter, code) return my_name diff --git a/pybamm/expression_tree/operations/evaluate_julia_old.py b/pybamm/expression_tree/operations/evaluate_julia_old.py deleted file mode 100644 index 0eea013f4b..0000000000 --- a/pybamm/expression_tree/operations/evaluate_julia_old.py +++ /dev/null @@ -1,1142 +0,0 @@ -# -# Write a symbol to Julia -# -import pybamm - -import numpy as np -import scipy.sparse -from collections import OrderedDict - -import numbers - - -def id_to_julia_variable(symbol_id, prefix): - """ - This function defines the format for the julia variable names used in find_symbols - and to_julia. Variable names are based on a nodes' id to make them unique - """ - var_format = prefix + "_{:05d}" - # Need to replace "-" character to make them valid julia variable names - return var_format.format(symbol_id).replace("-", "m") - - -def is_constant_and_can_evaluate(symbol): - """ - Returns True if symbol is constant and evaluation does not raise any errors. - Returns False otherwise. - An example of a constant symbol that cannot be "evaluated" is PrimaryBroadcast(0). - """ - if symbol.is_constant(): - try: - symbol.evaluate() - return True - except NotImplementedError: - return False - else: - return False - - -def find_symbols( - symbol, - constant_symbols, - variable_symbols, - variable_symbol_sizes, - round_constants=True, -): - """ - This function converts an expression tree to a dictionary of node id's and strings - specifying valid julia code to calculate that nodes value, given y and t. - - The function distinguishes between nodes that represent constant nodes in the tree - (e.g. a pybamm.Matrix), and those that are variable (e.g. subtrees that contain - pybamm.StateVector). The former are put in `constant_symbols`, the latter in - `variable_symbols` - - Note that it is important that the arguments `constant_symbols` and - `variable_symbols` be and *ordered* dict, since the final ordering of the code lines - are important for the calculations. A dict is specified rather than a list so that - identical subtrees (which give identical id's) are not recalculated in the code - - Parameters - ---------- - symbol : :class:`pybamm.Symbol` - The symbol or expression tree to convert - - constant_symbol : collections.OrderedDict - The output dictionary of constant symbol ids to lines of code - - variable_symbol : collections.OrderedDict - The output dictionary of variable (with y or t) symbol ids to lines of code - - variable_symbol_sizes : collections.OrderedDict - The output dictionary of variable (with y or t) symbol ids to size of that - variable, for caching - - """ - # ignore broadcasts for now - if isinstance(symbol, pybamm.Broadcast): - symbol = symbol.child - if is_constant_and_can_evaluate(symbol): - value = symbol.evaluate() - if round_constants: - value = np.round(value, decimals=11) - if not isinstance(value, numbers.Number): - if scipy.sparse.issparse(value): - # Create Julia SparseArray - row, col, data = scipy.sparse.find(value) - if round_constants: - data = np.round(data, decimals=11) - m, n = value.shape - # Set print options large enough to avoid ellipsis - # at least as big is len(row) = len(col) = len(data) - np.set_printoptions( - threshold=max(np.get_printoptions()["threshold"], len(row) + 10) - ) - # increase precision for printing - np.set_printoptions(precision=20) - # add 1 to correct for 1-indexing in Julia - # use array2string so that commas are included - constant_symbols[symbol.id] = "sparse({}, {}, {}, {}, {})".format( - np.array2string(row + 1, separator=","), - np.array2string(col + 1, separator=","), - np.array2string(data, separator=","), - m, - n, - ) - variable_symbol_sizes[symbol.id] = symbol.shape - elif value.shape == (1, 1): - # Extract value if array has only one entry - constant_symbols[symbol.id] = value[0, 0] - variable_symbol_sizes[symbol.id] = 1 - elif value.shape[1] == 1: - # Set print options large enough to avoid ellipsis - # at least as big as len(row) = len(col) = len(data) - np.set_printoptions( - threshold=max( - np.get_printoptions()["threshold"], value.shape[0] + 10 - ) - ) - # Flatten a 1D array - constant_symbols[symbol.id] = np.array2string( - value.flatten(), separator="," - ) - variable_symbol_sizes[symbol.id] = symbol.shape[0] - else: - constant_symbols[symbol.id] = value - # No need to save the size as it will not need to be used - return - - # process children recursively - for child in symbol.children: - find_symbols( - child, - constant_symbols, - variable_symbols, - variable_symbol_sizes, - round_constants=round_constants, - ) - - # calculate the variable names that will hold the result of calculating the - # children variables - children_vars = [] - for child in symbol.children: - if isinstance(child, pybamm.Broadcast): - child = child.child - if is_constant_and_can_evaluate(child): - child_eval = child.evaluate() - if isinstance(child_eval, numbers.Number): - children_vars.append(str(child_eval)) - else: - children_vars.append(id_to_julia_variable(child.id, "const")) - else: - children_vars.append(id_to_julia_variable(child.id, "cache")) - - if isinstance(symbol, pybamm.BinaryOperator): - # TODO: we can pass through a dummy y and t to get the type and then hardcode - # the right line, avoiding these checks - if isinstance(symbol, pybamm.MatrixMultiplication): - symbol_str = "{0} @ {1}".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Inner): - symbol_str = "{0} * {1}".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Minimum): - symbol_str = "min({},{})".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Maximum): - symbol_str = "max({},{})".format(children_vars[0], children_vars[1]) - elif isinstance(symbol, pybamm.Power): - # julia uses ^ instead of ** for power - # include dot for elementwise operations - symbol_str = children_vars[0] + " .^ " + children_vars[1] - else: - # all other operations use the same symbol - symbol_str = children_vars[0] + " " + symbol.name + " " + children_vars[1] - - elif isinstance(symbol, pybamm.UnaryOperator): - # Index has a different syntax than other univariate operations - if isinstance(symbol, pybamm.Index): - # Because of how julia indexing works, add 1 to the start, but not to the - # stop - symbol_str = "{}[{}:{}]".format( - children_vars[0], symbol.slice.start + 1, symbol.slice.stop - ) - elif isinstance(symbol, pybamm.Gradient): - symbol_str = "grad_{}({})".format(tuple(symbol.domain), children_vars[0]) - elif isinstance(symbol, pybamm.Divergence): - symbol_str = "div_{}({})".format(tuple(symbol.domain), children_vars[0]) - elif isinstance(symbol, pybamm.Broadcast): - # ignore broadcasts for now - symbol_str = children_vars[0] - elif isinstance(symbol, pybamm.BoundaryValue): - symbol_str = "boundary_value_{}({})".format(symbol.side, children_vars[0]) - else: - symbol_str = symbol.name + children_vars[0] - - elif isinstance(symbol, pybamm.Function): - # write functions directly - symbol_str = "{}({})".format(symbol.julia_name, ", ".join(children_vars)) - - elif isinstance(symbol, (pybamm.Variable, pybamm.ConcatenationVariable)): - # No need to do anything if a Variable is found - return - - elif isinstance(symbol, pybamm.Concatenation): - if isinstance(symbol, (pybamm.NumpyConcatenation, pybamm.SparseStack)): - # return a list of the children variables, which will be converted to a - # line by line assignment - # return this as a string so that other functionality still works - # also save sizes - symbol_str = "[" - for child in children_vars: - child_id = child[6:].replace("m", "-") - size = variable_symbol_sizes[int(child_id)] - symbol_str += "{}::{}, ".format(size, child) - symbol_str = symbol_str[:-2] + "]" - - # DomainConcatenation specifies a particular ordering for the concatenation, - # which we must follow - elif isinstance(symbol, pybamm.DomainConcatenation): - if symbol.secondary_dimensions_npts == 1: - all_child_vectors = children_vars - all_child_sizes = [ - variable_symbol_sizes[int(child[6:].replace("m", "-"))] - for child in children_vars - ] - else: - slice_starts = [] - all_child_vectors = [] - all_child_sizes = [] - for i in range(symbol.secondary_dimensions_npts): - child_vectors = [] - child_sizes = [] - for child_var, slices in zip( - children_vars, symbol._children_slices - ): - for child_dom, child_slice in slices.items(): - slice_starts.append(symbol._slices[child_dom][i].start) - # add 1 to slice start to account for julia indexing - child_vectors.append( - "@view {}[{}:{}]".format( - child_var, - child_slice[i].start + 1, - child_slice[i].stop, - ) - ) - child_sizes.append( - child_slice[i].stop - child_slice[i].start - ) - all_child_vectors.extend( - [v for _, v in sorted(zip(slice_starts, child_vectors))] - ) - all_child_sizes.extend( - [v for _, v in sorted(zip(slice_starts, child_sizes))] - ) - # return a list of the children variables, which will be converted to a - # line by line assignment - # return this as a string so that other functionality still works - # also save sizes - symbol_str = "[" - for child, size in zip(all_child_vectors, all_child_sizes): - symbol_str += "{}::{}, ".format(size, child) - symbol_str = symbol_str[:-2] + "]" - - else: - # A regular Concatenation for the MTK model - # We will define the concatenation function separately - symbol_str = "concatenation(" + ", ".join(children_vars) + ")" - - # Note: we assume that y is being passed as a column vector - elif isinstance(symbol, pybamm.StateVectorBase): - if isinstance(symbol, pybamm.StateVector): - name = "@view y" - elif isinstance(symbol, pybamm.StateVectorDot): - name = "@view dy" - indices = np.argwhere(symbol.evaluation_array).reshape(-1).astype(np.int32) - # add 1 since julia uses 1-indexing - indices += 1 - if len(indices) == 1: - symbol_str = "{}[{}]".format(name, indices[0]) - else: - # julia does include the final value - symbol_str = "{}[{}:{}]".format(name, indices[0], indices[-1]) - - elif isinstance(symbol, pybamm.Time): - symbol_str = "t" - - elif isinstance(symbol, pybamm.InputParameter): - symbol_str = "inputs['{}']".format(symbol.name) - - elif isinstance(symbol, pybamm.SpatialVariable): - symbol_str = symbol.name - - elif isinstance(symbol, pybamm.FunctionParameter): - symbol_str = "{}({})".format(symbol.name, ", ".join(children_vars)) - - else: - raise NotImplementedError( - "Conversion to Julia not implemented for a symbol of type '{}'".format( - type(symbol) - ) - ) - - variable_symbols[symbol.id] = symbol_str - - # Save the size of the symbol - try: - if symbol.shape == (): - variable_symbol_sizes[symbol.id] = 1 - else: - variable_symbol_sizes[symbol.id] = symbol.shape[0] - except NotImplementedError: - pass - - -def to_julia(symbol, round_constants=True): - """ - This function converts an expression tree into a dict of constant input values, and - valid julia code that acts like the tree's :func:`pybamm.Symbol.evaluate` function - - Parameters - ---------- - symbol : :class:`pybamm.Symbol` - The symbol to convert to julia code - - Returns - ------- - constant_values : collections.OrderedDict - dict mapping node id to a constant value. Represents all the constant nodes in - the expression tree - str - valid julia code that will evaluate all the variable nodes in the tree. - - """ - - constant_values = OrderedDict() - variable_symbols = OrderedDict() - variable_symbol_sizes = OrderedDict() - find_symbols( - symbol, - constant_values, - variable_symbols, - variable_symbol_sizes, - round_constants=round_constants, - ) - - return constant_values, variable_symbols, variable_symbol_sizes - - -def get_julia_function( - symbol, - funcname="f", - input_parameter_order=None, - len_rhs=None, - preallocate=True, - round_constants=True, - cache_type="standard" -): - """ - Converts a pybamm expression tree into pure julia code that will calculate the - result of calling `evaluate(t, y)` on the given expression tree. - - Parameters - ---------- - symbol : :class:`pybamm.Symbol` - The symbol to convert to julia code - funcname : str, optional - The name to give to the function (default 'f') - input_parameter_order : list, optional - List of input parameter names. Defines the order in which the input parameters - are extracted from 'p' in the julia function that is created - len_rhs : int, optional - The number of ODEs in the discretized differential equations. This also - determines whether the model has any algebraic equations: if None (default), - the model is assume to have no algebraic parts and ``julia_str`` is compatible - with an ODE solver. If not None, ``julia_str`` is compatible with a DAE solver - preallocate : bool, optional - Whether to write the function in a way that preallocates memory for the output. - Default is True, which is faster. Must be False for the function to be - modelingtoolkitized. - cache_type : str, optional - The type of cache to use for the function. Must be one of 'standard', 'dual', 'symbolic', - or 'gpu'. If 'standard', the function will be cached in the standard way, - If 'dual', the function will use the dualcache provided by preallocationtools.jl, - and if 'symbolic', the function will use the symcache provided by PyBaMM.jl. Default - is standard, and as of so far, I haven't been able to beat it with performance yet. - - Returns - ------- - julia_str : str - String of julia code, to be evaluated by ``julia.Main.eval`` - - """ - if len_rhs is None: - typ = "ode" - else: - typ = "dae" - # Take away dy from the differential states - # we will return a function of the form - # out[] = .. - dy[] for the differential states - # out[] = .. for the algebraic states - symbol_minus_dy = [] - end = 0 - for child in symbol.orphans: - start = end - end += child.size - if end <= len_rhs: - symbol_minus_dy.append(child - pybamm.StateVectorDot(slice(start, end))) - else: - symbol_minus_dy.append(child) - symbol = pybamm.numpy_concatenation(*symbol_minus_dy) - constants, var_symbols, var_symbol_sizes = to_julia( - symbol, round_constants=round_constants - ) - - # extract constants in generated function - const_and_cache_str = "cs = (\n" - shorter_const_names = {} - for i_const, (symbol_id, const_value) in enumerate(constants.items()): - const_name = id_to_julia_variable(symbol_id, "const") - const_name_short = "const_{}".format(i_const) - if cache_type=="gpu": - const_and_cache_str += " {} = cu({}),\n".format(const_name_short, const_value) - else: - const_and_cache_str += " {} = {},\n".format(const_name_short, const_value) - shorter_const_names[const_name] = const_name_short - - # Pop (get and remove) items from the dictionary of symbols one by one - # If they are simple operations (@view, +, -, *, /), replace all future - # occurences instead of assigning them. This "inlining" speeds up the computation - inlineable_symbols = ["@view", "+", "-", "*", "/"] - var_str = "" - input_parameters = {} - while var_symbols: - var_symbol_id, symbol_line = var_symbols.popitem(last=False) - julia_var = id_to_julia_variable(var_symbol_id, "cache") - # Look for lists in the variable symbols. These correspond to concatenations, so - # assign the children to the right parts of the vector - #symbol_line_split = symbol_line.split(", ") - #var_str += "{} = get_tmp(cs.{},{})\n".format(julia_var,julia_var,symbol_line) - if symbol_line[0] == "[" and symbol_line[-1] == "]": - # convert to actual list - symbol_line = symbol_line[1:-1].split(", ") - new_list = [] - #make sure we haven't split a tuple - kill=False - for i,thing in enumerate(symbol_line): - if kill: - continue - elif thing[0] == '(': - next_thing = symbol_line[i+1] - thing = thing+next_thing - new_list.append(thing) - symbol_line = new_list - start = 0 - if preallocate is True or var_symbol_id == symbol.id: - for child_size_and_name in symbol_line: - child_size, child_name = child_size_and_name.split("::") - end = start + int(child_size) - # add 1 to start to account for julia 1-indexing - var_str += "@. {}[{}:{}] = {}\n".format( - julia_var, start + 1, end, child_name - ) - start = end - else: - concat_str = "{} = vcat(".format(julia_var) - for i, child_size_and_name in enumerate(symbol_line): - child_size, child_name = child_size_and_name.split("::") - var_str += "x{} = @. {}\n".format(i + 1, child_name) - concat_str += "x{}, ".format(i + 1) - var_str += concat_str[:-2] + ")\n" - # use mul! for matrix multiplications (requires LinearAlgebra library) - elif " @ " in symbol_line: - if preallocate is False: - symbol_line = symbol_line.replace(" @ ", " * ") - var_str += "{} = {}\n".format(julia_var, symbol_line) - else: - symbol_line = symbol_line.replace(" @ ", ", ") - var_str += "mul!({}, {})\n".format(julia_var, symbol_line) - # find input parameters - elif symbol_line.startswith("inputs"): - input_parameters[julia_var] = symbol_line[8:-2] - elif "minimum" in symbol_line or "maximum" in symbol_line: - var_str += "{} .= {}\n".format(julia_var, symbol_line) - else: - # don't replace the matrix multiplication cases (which will be - # turned into a mul!), since it is faster to assign to a cache array - # first in that case - # e.g. mul!(cs.cache_1, cs.cache_2, cs.cache_3) - # unless it is a @view in which case we don't - # need to cache - # e.g. mul!(cs.cache_1, cs.cache_2, @view y[1:10]) - # also don't replace the minimum() or maximum() cases as we can't - # broadcast them - any_matmul_min_max = any( - julia_var in next_symbol_line - and ( - any( - x in next_symbol_line - for x in [" @ ", "mul!", "minimum", "maximum"] - ) - and not symbol_line.startswith("@view") - ) - for next_symbol_line in var_symbols.values() - ) - # inline operation if it can be inlined - if ( - any(x in symbol_line for x in inlineable_symbols) or symbol_line == "t" - ) and not any_matmul_min_max: - found_replacement = False - # replace all other occurrences of the variable - # in the dictionary with the symbol line - for next_var_id, next_symbol_line in var_symbols.items(): - if julia_var in next_symbol_line: - if symbol_line == "t": - # no brackets needed - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, symbol_line - ) - else: - # add brackets so that the order of operations is maintained - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, "({})".format(symbol_line) - ) - found_replacement = True - if not found_replacement: - var_str += "@. {} = {}\n".format(julia_var, symbol_line) - - # otherwise assign - else: - var_str += "@. {} = {}\n".format(julia_var, symbol_line) - # Replace all input parameter names - for input_parameter_id, input_parameter_name in input_parameters.items(): - var_str = var_str.replace(input_parameter_id, input_parameter_name) - - # indent code - var_str = " " + var_str - var_str = var_str.replace("\n", "\n ") - - - cache_initialization_str = "" - - # add the cache variables to the cache NamedTuple - i_cache = 0 - for var_symbol_id, var_symbol_size in var_symbol_sizes.items(): - # Skip caching the result variable since this is provided as dy - # Also skip caching the result variable if it doesn't appear in the var_str, - # since it has been inlined and does not need to be assigned to - julia_var = id_to_julia_variable(var_symbol_id, "cache") - if var_symbol_id != symbol.id and julia_var in var_str: - julia_var_short = "cache_{}".format(i_cache) - var_str = var_str.replace(julia_var, julia_var_short) - i_cache += 1 - if preallocate is True: - if cache_type == "symbolic": - const_and_cache_str += " {} = symcache(zeros({}),Vector{{Num}}(undef,{})),\n".format( - julia_var_short, var_symbol_size,var_symbol_size - ) - cache_initialization_str += " {} = get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) - elif cache_type == "standard": - const_and_cache_str += " {} = zeros({}),\n".format( - julia_var_short, var_symbol_size - ) - elif cache_type == "dual": - const_and_cache_str += " {} = dualcache(zeros({}),12),\n".format( - julia_var_short, var_symbol_size - ) - cache_initialization_str += " {} = PreallocationTools.get_tmp(cs.{},(@view y[1:{}]))\n".format(julia_var_short,julia_var_short,var_symbol_size) - elif cache_type == "gpu": - const_and_cache_str+=" {} = CUDA.zeros({}),\n".format(julia_var_short,var_symbol_size) - - else: - # Cache variables have not been preallocated - var_str = var_str.replace( - "@. {} = ".format(julia_var_short), - "{} = @. ".format(julia_var_short), - ) - - # Shorten the name of the constants from id to const_0, const_1, etc. - for long, short in shorter_const_names.items(): - var_str = var_str.replace(long, "cs." + short) - - # close the constants and cache string - const_and_cache_str += ")\n" - - # remove the constant and cache sring if it is empty - const_and_cache_str = const_and_cache_str.replace("cs = (\n)\n", "") - - # calculate the final variable that will output the result - if symbol.is_constant(): - result_var = id_to_julia_variable(symbol.id, "const") - if result_var in shorter_const_names: - result_var = shorter_const_names[result_var] - result_value = symbol.evaluate() - if isinstance(result_value, numbers.Number): - var_str = var_str + "\n dy .= " + str(result_value) + "\n" - else: - var_str = var_str + "\n dy .= cs." + result_var + "\n" - else: - result_var = id_to_julia_variable(symbol.id, "cache") - if typ == "ode": - out = "dy" - elif typ == "dae": - out = "out" - # replace "cache_123 = ..." with "dy .= ..." (ensure we allocate to the - # variable that was passed in) - var_str = var_str.replace(f" {result_var} =", f" {out} .=") - # catch other cases for dy - var_str = var_str.replace(result_var, out) - - # add "cs." to cache names - if preallocate is True: - if cache_type in ["standard","gpu"]: - var_str = var_str.replace("cache", "cs.cache") - - # line that extracts the input parameters in the right order - if input_parameter_order is None: - input_parameter_extraction = "" - elif len(input_parameter_order) == 1: - # extract the single parameter - input_parameter_extraction = " " + input_parameter_order[0] + " = p[1]\n" - else: - # extract all parameters - input_parameter_extraction = " " + ", ".join(input_parameter_order) + " = p\n" - - if preallocate is False or const_and_cache_str == "": - func_def = f"{funcname}!" - else: - func_def = f"{funcname}_with_consts!" - - # add function def - if typ == "ode": - function_def = f"\nfunction {func_def}(dy, y, p, t)\n" - elif typ == "dae": - function_def = f"\nfunction {func_def}(out, dy, y, p, t)\n" - julia_str = ( - "begin\n" - + const_and_cache_str - + function_def - + cache_initialization_str - + input_parameter_extraction - + var_str - ) - - # close the function, with a 'nothing' to avoid allocations - julia_str += "nothing\nend\n\n" - julia_str = julia_str.replace("\n \n", "\n") - - if not (preallocate is False or const_and_cache_str == ""): - # Use a let block for the cached variables - # open the let block - julia_str = julia_str.replace("cs = (", f"{funcname}! = let cs = (") - # close the let block - julia_str += "end\n" - - # close the "begin" - julia_str += "end" - - return julia_str - - -def convert_var_and_eqn_to_str(var, eqn, all_constants_str, all_variables_str, typ): - """ - Converts a variable and its equation to a julia string - - Parameters - ---------- - var : :class:`pybamm.Symbol` - The variable (key in the dictionary of rhs/algebraic/initial conditions) - eqn : :class:`pybamm.Symbol` - The equation (value in the dictionary of rhs/algebraic/initial conditions) - all_constants_str : str - String containing all the constants defined so far - all_variables_str : str - String containing all the variables defined so far - typ : str - The type of the variable/equation pair being converted ("equation", "initial - condition", or "boundary condition") - - Returns - ------- - all_constants_str : str - Updated string of all constants - all_variables_str : str - Updated string of all variables - eqn_str : str - The string describing the final equation result, perhaps as a function of some - variables and/or constants in all_constants_str and all_variables_str - - """ - if isinstance(eqn, pybamm.Broadcast): - # ignore broadcasts for now - eqn = eqn.child - - var_symbols = to_julia(eqn)[1] - - # var_str = "" - # for symbol_id, symbol_line in var_symbols.items(): - # var_str += f"{id_to_julia_variable(symbol_id)} = {symbol_line}\n" - # Pop (get and remove) items from the dictionary of symbols one by one - # If they are simple operations (+, -, *, /), replace all future - # occurences instead of assigning them. - inlineable_symbols = [" + ", " - ", " * ", " / "] - var_str = "" - while var_symbols: - var_symbol_id, symbol_line = var_symbols.popitem(last=False) - julia_var = id_to_julia_variable(var_symbol_id, "cache") - # inline operation if it can be inlined - if "concatenation" not in symbol_line: - found_replacement = False - # replace all other occurrences of the variable - # in the dictionary with the symbol line - for next_var_id, next_symbol_line in var_symbols.items(): - if ( - symbol_line == "t" - or " " not in symbol_line - or symbol_line.startswith("grad") - or not any(x in next_symbol_line for x in inlineable_symbols) - ): - # cases that don't need brackets - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, symbol_line - ) - elif next_symbol_line.startswith("concatenation"): - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, f"\n {symbol_line}\n" - ) - else: - # add brackets so that the order of operations is maintained - var_symbols[next_var_id] = next_symbol_line.replace( - julia_var, "({})".format(symbol_line) - ) - found_replacement = True - if not found_replacement: - var_str += "{} = {}\n".format(julia_var, symbol_line) - - # otherwise assign - else: - var_str += "{} = {}\n".format(julia_var, symbol_line) - - # If we have created a concatenation we need to define it - # Hardcoded to the negative electrode, separator, positive electrode case for now - if "concatenation" in var_str and "function concatenation" not in all_variables_str: - concatenation_def = ( - "\nfunction concatenation(n, s, p)\n" - + " # A concatenation in the electrolyte domain\n" - + " IfElse.ifelse(\n" - + " x < neg_width, n, IfElse.ifelse(\n" - + " x < neg_plus_sep_width, s, p\n" - + " )\n" - + " )\n" - + "end\n" - ) - else: - concatenation_def = "" - - # Define the FunctionParameter objects that have not yet been defined - function_defs = "" - for x in eqn.pre_order(): - if ( - isinstance(x, pybamm.FunctionParameter) - and f"function {x.name}" not in all_variables_str - and typ == "equation" - ): - function_def = ( - f"\nfunction {x.name}(" - + ", ".join(x.arg_names) - + ")\n" - + " {}\n".format(str(x.callable).replace("**", "^")) - + "end\n" - ) - function_defs += function_def - - if concatenation_def + function_defs != "": - function_defs += "\n" - - var_str = concatenation_def + function_defs + var_str - - # add a comment labeling the equation, and the equation itself - if var_str == "": - all_variables_str += "" - else: - all_variables_str += f"# '{var.name}' {typ}\n" + var_str + "\n" - - # calculate the final variable that will output the result - if eqn.is_constant(): - result_var = id_to_julia_variable(eqn.id, "const") - else: - result_var = id_to_julia_variable(eqn.id, "cache") - if is_constant_and_can_evaluate(eqn): - result_value = eqn.evaluate() - else: - result_value = None - - # define the variable that goes into the equation - if eqn.is_constant() and isinstance(result_value, numbers.Number): - eqn_str = str(result_value) - else: - eqn_str = result_var - - return all_constants_str, all_variables_str, eqn_str - - -def get_julia_mtk_model(model, geometry=None, tspan=None): - """ - Converts a pybamm model into a Julia ModelingToolkit model - - Parameters - ---------- - model : :class:`pybamm.BaseModel` - The model to be converted - geometry : dict, optional - Dictionary defining the geometry. Must be provided if the model is a PDE model - tspan : array-like, optional - Time for which to solve the model. Must be provided if the model is a PDE model - - Returns - ------- - mtk_str : str - String of julia code representing a model in MTK, - to be evaluated by ``julia.Main.eval`` - """ - # Extract variables - variables = {**model.rhs, **model.algebraic}.keys() - variable_to_print_name = {} - for i, var in enumerate(variables): - if var.print_name is not None: - print_name = var._raw_print_name - else: - print_name = f"u{i+1}" - variable_to_print_name[var] = print_name - if isinstance(var, pybamm.ConcatenationVariable): - for child in var.children: - variable_to_print_name[child] = print_name - - # Extract domain and auxiliary domains - all_domains = set( - [tuple(dom) for var in variables for dom in var.domains.values() if dom != []] - ) - is_pde = bool(all_domains) - - # Check geometry and tspan have been provided if a PDE - if is_pde: - if geometry is None: - raise ValueError("must provide geometry if the model is a PDE model") - if tspan is None: - raise ValueError("must provide tspan if the model is a PDE model") - - # Read domain names - domain_name_to_symbol = {} - long_domain_symbol_to_short = {} - for dom in all_domains: - # Read domain name from geometry - domain_symbol = list(geometry[dom[0]].keys())[0] - if len(dom) > 1: - domain_symbol = domain_symbol[0] - # For multi-domain variables keep only the first letter of the domain - domain_name_to_symbol[tuple(dom)] = domain_symbol - # Record which domain symbols we shortened - for d in dom: - long = list(geometry[d].keys())[0] - long_domain_symbol_to_short[long] = domain_symbol - else: - # Otherwise keep the whole domain - domain_name_to_symbol[tuple(dom)] = domain_symbol - - # Read domain limits - domain_name_to_limits = {(): None} - for dom in all_domains: - limits = list(geometry[dom[0]].values())[0].values() - if len(limits) > 1: - lower_limit, _ = list(geometry[dom[0]].values())[0].values() - _, upper_limit = list(geometry[dom[-1]].values())[0].values() - domain_name_to_limits[tuple(dom)] = ( - lower_limit.evaluate(), - upper_limit.evaluate(), - ) - else: - # Don't record limits for variables that have "limits" of length 1 i.e. - # a zero-dimensional domain - domain_name_to_limits[tuple(dom)] = None - - # Define independent variables for each variable - var_to_ind_vars = {} - var_to_ind_vars_left_boundary = {} - var_to_ind_vars_right_boundary = {} - for var in variables: - if var.domain in [[], ["current collector"]]: - var_to_ind_vars[var] = "(t)" - else: - # all independent variables e.g. (t, x) or (t, rn, xn) - domain_symbols = ", ".join( - domain_name_to_symbol[tuple(dom)] - for dom in var.domains.values() - if domain_name_to_limits[tuple(dom)] is not None - ) - var_to_ind_vars[var] = f"(t, {domain_symbols})" - if isinstance(var, pybamm.ConcatenationVariable): - for child in var.children: - var_to_ind_vars[child] = f"(t, {domain_symbols})" - aux_domain_symbols = ", ".join( - domain_name_to_symbol[tuple(dom)] - for level, dom in var.domains.items() - if level != "primary" and domain_name_to_limits[tuple(dom)] is not None - ) - if aux_domain_symbols != "": - aux_domain_symbols = ", " + aux_domain_symbols - - limits = domain_name_to_limits[tuple(var.domain)] - # left bc e.g. (t, 0) or (t, 0, xn) - var_to_ind_vars_left_boundary[var] = f"(t, {limits[0]}{aux_domain_symbols})" - # right bc e.g. (t, 1) or (t, 1, xn) - var_to_ind_vars_right_boundary[ - var - ] = f"(t, {limits[1]}{aux_domain_symbols})" - - mtk_str = "begin\n" - # Define parameters (including independent variables) - # Makes a line of the form '@parameters t x1 x2 x3 a b c d' - ind_vars = ["t"] + [ - sym - for dom, sym in domain_name_to_symbol.items() - if domain_name_to_limits[dom] is not None - ] - for domain_name, domain_symbol in domain_name_to_symbol.items(): - if domain_name_to_limits[domain_name] is not None: - mtk_str += f"# {domain_name} -> {domain_symbol}\n" - mtk_str += "@parameters " + " ".join(ind_vars) - if len(model.input_parameters) > 0: - mtk_str += "\n# Input parameters\n@parameters" - for param in model.input_parameters: - mtk_str += f" {param.name}" - mtk_str += "\n" - - # Add a comment with the variable names - for var in variables: - mtk_str += f"# '{var.name}' -> {variable_to_print_name[var]}\n" - # Makes a line of the form '@variables u1(t) u2(t)' - dep_vars = [] - mtk_str += "@variables" - for var in variables: - mtk_str += f" {variable_to_print_name[var]}(..)" - dep_var = variable_to_print_name[var] + var_to_ind_vars[var] - dep_vars.append(dep_var) - mtk_str += "\n" - - # Define derivatives - for domain_symbol in ind_vars: - mtk_str += f"D{domain_symbol} = Differential({domain_symbol})\n" - mtk_str += "\n" - - # Define equations - all_eqns_str = "" - all_constants_str = "" - all_julia_str = "" - for var, eqn in {**model.rhs, **model.algebraic}.items(): - all_constants_str, all_julia_str, eqn_str = convert_var_and_eqn_to_str( - var, eqn, all_constants_str, all_julia_str, "equation" - ) - - if var in model.rhs: - all_eqns_str += ( - f" Dt({variable_to_print_name[var]}{var_to_ind_vars[var]}) " - + f"~ {eqn_str},\n" - ) - elif var in model.algebraic: - all_eqns_str += f" 0 ~ {eqn_str},\n" - - # Replace any long domain symbols with the short version - # e.g. "xn" gets replaced with "x" - for long, short in long_domain_symbol_to_short.items(): - # we need to add a space to avoid accidentally replacing 'exp' with 'ex' - all_julia_str = all_julia_str.replace(" " + long, " " + short) - - # Replace variables in the julia strings that correspond to pybamm variables with - # their julia equivalent - for var, julia_id in variable_to_print_name.items(): - # e.g. boundary_value_right(cache_123456789) gets replaced with u1(t, 1) - cache_var_id = id_to_julia_variable(var.id, "cache") - if f"boundary_value_right({cache_var_id})" in all_julia_str: - all_julia_str = all_julia_str.replace( - f"boundary_value_right({cache_var_id})", - julia_id + var_to_ind_vars_right_boundary[var], - ) - # e.g. cache_123456789 gets replaced with u1(t, x) - all_julia_str = all_julia_str.replace( - cache_var_id, julia_id + var_to_ind_vars[var] - ) - - # Replace independent variables (domain names) in julia strings with the - # corresponding symbol - for domain_name, domain_symbol in domain_name_to_symbol.items(): - all_julia_str = all_julia_str.replace( - f"grad_{domain_name}", f"D{domain_symbol}" - ) - # Different divergence depending on the coordinate system - coord_sys = getattr(pybamm.standard_spatial_vars, domain_symbol).coord_sys - if coord_sys == "cartesian": - all_julia_str = all_julia_str.replace( - f"div_{domain_name}", f"D{domain_symbol}" - ) - elif coord_sys == "spherical polar": - all_julia_str = all_julia_str.replace( - f"div_{domain_name}(", - f"1 / {domain_symbol}^2 * D{domain_symbol}({domain_symbol}^2 * ", - ) - - # Replace the thicknesses in the concatenation with the actual thickness from the - # geometry - if "neg_width" in all_julia_str or "neg_plus_sep_width" in all_julia_str: - var = pybamm.standard_spatial_vars - x_n = geometry["negative electrode"]["x_n"]["max"].evaluate() - x_s = geometry["separator"]["x_s"]["max"].evaluate() - all_julia_str = all_julia_str.replace("neg_width", str(x_n)) - all_julia_str = all_julia_str.replace("neg_plus_sep_width", str(x_s)) - - # Update the MTK string - mtk_str += all_constants_str + all_julia_str + "\n" + f"eqs = [\n{all_eqns_str}]\n" - - #################################################################################### - # Initial and boundary conditions - #################################################################################### - # Initial conditions - all_ic_bc_str = " # initial conditions\n" - all_ic_bc_constants_str = "" - all_ic_bc_julia_str = "" - for var, eqn in model.initial_conditions.items(): - ( - all_ic_bc_constants_str, - all_ic_bc_julia_str, - eqn_str, - ) = convert_var_and_eqn_to_str( - var, eqn, all_ic_bc_constants_str, all_ic_bc_julia_str, "initial condition" - ) - - if not is_pde: - all_ic_bc_str += f" {variable_to_print_name[var]}(t) => {eqn_str},\n" - else: - if var.domain == []: - doms = "" - else: - doms = ", " + domain_name_to_symbol[tuple(var.domain)] - - all_ic_bc_str += f" {variable_to_print_name[var]}(0{doms}) ~ {eqn_str},\n" - # Boundary conditions - if is_pde: - all_ic_bc_str += " # boundary conditions\n" - for var, eqn_side in model.boundary_conditions.items(): - if isinstance(var, (pybamm.Variable, pybamm.ConcatenationVariable)): - for side, (eqn, typ) in eqn_side.items(): - ( - all_ic_bc_constants_str, - all_ic_bc_julia_str, - eqn_str, - ) = convert_var_and_eqn_to_str( - var, - eqn, - all_ic_bc_constants_str, - all_ic_bc_julia_str, - "boundary condition", - ) - - if side == "left": - limit = var_to_ind_vars_left_boundary[var] - elif side == "right": - limit = var_to_ind_vars_right_boundary[var] - - bc = f"{variable_to_print_name[var]}{limit}" - if typ == "Dirichlet": - bc = bc - elif typ == "Neumann": - bc = f"D{domain_name_to_symbol[tuple(var.domain)]}({bc})" - all_ic_bc_str += f" {bc} ~ {eqn_str},\n" - - # Replace variables in the julia strings that correspond to pybamm variables with - # their julia equivalent - for var, julia_id in variable_to_print_name.items(): - # e.g. boundary_value_right(cache_123456789) gets replaced with u1(t, 1) - cache_var_id = id_to_julia_variable(var.id, "cache") - if f"boundary_value_right({cache_var_id})" in all_ic_bc_julia_str: - all_ic_bc_julia_str = all_ic_bc_julia_str.replace( - f"boundary_value_right({cache_var_id})", - julia_id + var_to_ind_vars_right_boundary[var], - ) - # e.g. cache_123456789 gets replaced with u1(t, x) - all_ic_bc_julia_str = all_ic_bc_julia_str.replace( - cache_var_id, julia_id + var_to_ind_vars[var] - ) - - #################################################################################### - - # Create ODESystem or PDESystem - if not is_pde: - mtk_str += "sys = ODESystem(eqs, t)\n\n" - mtk_str += ( - all_ic_bc_constants_str - + all_ic_bc_julia_str - + "\n" - + f"u0 = [\n{all_ic_bc_str}]\n" - ) - else: - # Initial and boundary conditions - mtk_str += ( - all_ic_bc_constants_str - + all_ic_bc_julia_str - + "\n" - + f"ics_bcs = [\n{all_ic_bc_str}]\n" - ) - - # Domains - mtk_str += "\n" - tpsan_str = ",".join( - map(lambda x: f"{x / model.timescale.evaluate():.3f}", tspan) - ) - mtk_str += f"t_domain = Interval({tpsan_str})\n" - domains = "domains = [\n t in t_domain,\n" - for domain, symbol in domain_name_to_symbol.items(): - limits = domain_name_to_limits[tuple(domain)] - if limits is not None: - mtk_str += f"{symbol}_domain = Interval{limits}\n" - domains += f" {symbol} in {symbol}_domain,\n" - domains += "]\n" - - mtk_str += "\n" - mtk_str += domains - - # Independent and dependent variables - mtk_str += "ind_vars = [{}]\n".format(", ".join(ind_vars)) - mtk_str += "dep_vars = [{}]\n\n".format(", ".join(dep_vars)) - - name = model.name.replace(" ", "_").replace("-", "_") - mtk_str += ( - name - + "_pde_system = PDESystem(eqs, ics_bcs, domains, ind_vars, dep_vars)\n\n" - ) - - # Replace parameters in the julia strings in the form "inputs[name]" - # with just "name" - for param in model.input_parameters: - mtk_str = mtk_str.replace(f"inputs['{param.name}']", param.name) - - # Need to add 'nothing' to the end of the mtk string to avoid errors in MTK v4 - # See https://github.com/SciML/diffeqpy/issues/82 - mtk_str += "nothing\nend\n" - - return mtk_str From d5ce775e662dd830f73905bafebf8c7091413df1 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 11 Oct 2022 21:22:43 -0400 Subject: [PATCH 107/163] start fixing tests --- .../operations/evaluate_julia.py | 16 +- requirements.txt | 2 + .../test_operations/test_evaluate_julia.py | 4 +- .../test_base_model_generate_julia_diffeq.py | 9 +- tests/unit/test_solvers/test_julia_mtk.py | 240 ------------------ 5 files changed, 13 insertions(+), 258 deletions(-) delete mode 100644 tests/unit/test_solvers/test_julia_mtk.py diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 631b7b6ef1..15f6b8df13 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -8,7 +8,6 @@ from collections import OrderedDict from multimethod import multimethod from math import floor -import re import graphlib @@ -27,16 +26,13 @@ def is_constant_and_can_evaluate(symbol): Returns False otherwise. An example of a constant symbol that cannot be "evaluated" is PrimaryBroadcast(0). """ - try: - if symbol.is_constant(): - try: - symbol.evaluate() - return True - except NotImplementedError: - return False - else: + if symbol.is_constant(): + try: + symbol.evaluate() + return True + except NotImplementedError: return False - except: + else: return False diff --git a/requirements.txt b/requirements.txt index 831d301135..e7be2718d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,6 @@ sympy >= 1.8 # outside of plot() methods. # Should not be imported matplotlib >= 2.0 +# graphlib is a native part of python 3.9 and above +graphlib-backport; python_version < "3.9" # diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index ef643b971e..b030b6a17d 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -319,7 +319,7 @@ def test_evaluator_julia_discretised_operators(self): y_tests = [nodes**2 + 1, np.cos(nodes)] for i, expr in enumerate([grad_eqn_disc, div_eqn_disc]): - self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}_asf", decimal=8) + self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=8) def test_evaluator_julia_discretised_microscale(self): # create discretisation @@ -364,7 +364,7 @@ def test_evaluator_julia_discretised_microscale(self): y_tests = [np.linspace(0, 1, total_npts) ** 2] for i, expr in enumerate([grad_eqn_disc, div_eqn_disc]): - self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}_asdf", decimal=8) + self.evaluate_and_test_equal(expr, y_tests, funcname=f"f{i}", decimal=8) if __name__ == "__main__": diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index 371af20668..a3a021a2af 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -7,13 +7,10 @@ have_julia = True # pybamm.have_julia() if have_julia and platform.system() != "Windows": - from julia.api import Julia - - Julia(compiled_modules=False) - from julia import Main + from juliacall import Main # load julia libraries required for evaluating the strings - Main.eval("using SparseArrays, LinearAlgebra") + Main.seval("using SparseArrays, LinearAlgebra") @unittest.skipIf(not have_julia, "Julia not installed") @@ -31,7 +28,7 @@ def test_generate_ode(self): self.assertIsInstance(rhs_str, str) self.assertIn("ode_test_model", rhs_str) self.assertIn("(dy, y, p, t)", rhs_str) - self.assertIsInstance(ics_str, str) + self.assertIsInstance(ics_str, pybamm.Vector) self.assertIn("ode_test_model_u0", ics_str) self.assertIn("(u0, p)", ics_str) diff --git a/tests/unit/test_solvers/test_julia_mtk.py b/tests/unit/test_solvers/test_julia_mtk.py deleted file mode 100644 index 0e8f0838f2..0000000000 --- a/tests/unit/test_solvers/test_julia_mtk.py +++ /dev/null @@ -1,240 +0,0 @@ -# -# Test for the evaluate-to-Julia functions -# -import pybamm - -import unittest -from platform import system - - -# julia imports -have_julia = pybamm.have_julia() - - -@unittest.skipIf(not have_julia, "Julia not installed") -@unittest.skipIf(system() == "Windows", "Julia not supported on windows") -class TestCreateSolveMTKModel(unittest.TestCase): - """ - These tests just make sure there are no errors when calling - pybamm.get_julia_mtk_model. TODO: add (comment out) tests that run and solve the - model. This needs (i) faster import of diffeqpy, (ii) working PDE discretisations - in Julia. - """ - - def test_exponential_decay_model(self): - model = pybamm.BaseModel() - v = pybamm.Variable("v") - model.rhs = {v: -2 * v} - model.initial_conditions = {v: 0.5} - - pybamm.get_julia_mtk_model(model) - - # Main.eval("using ModelingToolkit") - # Main.eval(mtk_str) - - # Main.tspan = (0.0, 10.0) - # # this definition of prob doesn't work, so we use Main.eval instead - # # prob = de.ODEProblem(Main.sys, Main.u0, Main.tspan) - - # Main.eval("prob = ODEProblem(sys, u0, tspan)") - # sol = de.solve(Main.prob, de.Tsit5()) - - # y_sol = np.concatenate(sol.u) - # y_exact = 0.5 * np.exp(-2 * sol.t) - # np.testing.assert_almost_equal(y_sol, y_exact, decimal=6) - - def test_lotka_volterra_model(self): - model = pybamm.BaseModel() - a = pybamm.InputParameter("a") - b = pybamm.InputParameter("b") - c = pybamm.InputParameter("c") - d = pybamm.InputParameter("d") - x = pybamm.Variable("x") - y = pybamm.Variable("y") - - model.rhs = {x: a * x - b * x * y, y: c * x * y - d * y} - model.initial_conditions = {x: 1.0, y: 1.0} - - pybamm.get_julia_mtk_model(model) - - # # Solve using julia - # Main.eval("using ModelingToolkit") - # Main.eval(mtk_str) - - # Main.tspan = (0.0, 10.0) - # Main.eval( - # """ - # begin - # p = [a => 1.5, b => 1.0, c => 3.0, d => 1.0] - # prob = ODEProblem(sys, u0, tspan, p) - # end - # """ - # ) - # sol_julia = de.solve(Main.prob, de.Tsit5(), reltol=1e-8, abstol=1e-8) - - # y_sol_julia = np.vstack(sol_julia.u).T - - # # Solve using pybamm - # sol_pybamm = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8).solve( - # model, sol_julia.t, inputs={"a": 1.5, "b": 1.0, "c": 3.0, "d": 1.0} - # ) - - # # Compare - # np.testing.assert_almost_equal(y_sol_julia, sol_pybamm.y, decimal=5) - - def test_dae_model(self): - model = pybamm.BaseModel() - x = pybamm.Variable("x") - y = pybamm.Variable("y") - - model.rhs = {x: -2 * x} - model.algebraic = {y: x - y} - model.initial_conditions = {x: 1.0, y: 1.0} - - pybamm.get_julia_mtk_model(model) - - # # Solve using julia - # Main.eval("using ModelingToolkit") - # Main.eval(mtk_str) - - # Main.tspan = (0.0, 10.0) - # Main.eval("prob = ODEProblem(sys, u0, tspan)") - # sol_julia = de.solve(Main.prob, de.Rodas5(), reltol=1e-8, abstol=1e-8) - - # y_sol_julia = np.vstack(sol_julia.u).T - - # # Solve using pybamm - # sol_pybamm = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8).solve(model, - # sol_julia.t) - - # # Compare - # np.testing.assert_almost_equal(y_sol_julia, sol_pybamm.y, decimal=5) - - def test_pde_model(self): - model = pybamm.BaseModel() - var = pybamm.Variable("var", domain="line") - - model.rhs = {var: pybamm.div(pybamm.grad(var))} - model.initial_conditions = {var: 1.0} - model.boundary_conditions = { - var: {"left": (1, "Dirichlet"), "right": (1, "Dirichlet")} - } - - geometry = {"line": {"x": {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}}} - - pybamm.get_julia_mtk_model(model, geometry=geometry, tspan=(0.0, 10.0)) - - # # Solve using julia - # Main.eval("using ModelingToolkit, DiffEqOperators") - # Main.eval(mtk_str) - - # Main.tspan = (0.0, 10.0) - # # Method of lines discretization - # Main.dx = 0.1 - # Main.order = 2 - # Main.eval("discretization = MOLFiniteDifference(dx,order)") - - # # Convert the PDE problem into an ODE problem - # Main.eval("prob = DiffEqOperators.discretize(pde_system,discretization)") - - # # Solve PDE problem - # sol_julia = de.solve(Main.prob, de.Tsit5(), reltol=1e-8, abstol=1e-8) - - # y_sol_julia = np.hstack(sol_julia.u) - - # # Check everything is equal to 1 - # # Just a simple test for now to get started - # np.testing.assert_equal(y_sol_julia, 1) - - def test_pde_model_spherical_polar(self): - model = pybamm.BaseModel() - var = pybamm.Variable("var", domain="particle") - - model.rhs = {var: pybamm.div(pybamm.grad(var))} - model.initial_conditions = {var: 1.0} - model.boundary_conditions = { - var: {"left": (1, "Dirichlet"), "right": (1, "Dirichlet")} - } - - geometry = { - "particle": {"r_n": {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}} - } - - pybamm.get_julia_mtk_model(model, geometry=geometry, tspan=(0.0, 10.0)) - - # # Solve using julia - # Main.eval("using ModelingToolkit, DiffEqOperators") - # Main.eval(mtk_str) - - # Main.tspan = (0.0, 10.0) - # # Method of lines discretization - # Main.dx = 0.1 - # Main.order = 2 - # Main.eval("discretization = MOLFiniteDifference(dx,order)") - - # # Convert the PDE problem into an ODE problem - # Main.eval("prob = DiffEqOperators.discretize(pde_system,discretization)") - - # # Solve PDE problem - # sol_julia = de.solve(Main.prob, de.Tsit5(), reltol=1e-8, abstol=1e-8) - - # y_sol_julia = np.hstack(sol_julia.u) - - # # Check everything is equal to 1 - # # Just a simple test for now to get started - # np.testing.assert_equal(y_sol_julia, 1) - - def test_spm(self): - model = pybamm.lithium_ion.SPM() - parameter_values = model.default_parameter_values - parameter_values._replace_callable_function_parameters = False - sim = pybamm.Simulation(model, parameter_values=parameter_values) - sim.set_parameters() - pybamm.get_julia_mtk_model( - sim._model_with_set_params, geometry=sim.geometry, tspan=(0, 3600) - ) - - def test_spme(self): - model = pybamm.lithium_ion.SPMe() - parameter_values = model.default_parameter_values - parameter_values._replace_callable_function_parameters = False - sim = pybamm.Simulation(model, parameter_values=parameter_values) - sim.set_parameters() - pybamm.get_julia_mtk_model( - sim._model_with_set_params, geometry=sim.geometry, tspan=(0, 3600) - ) - - def test_dfn(self): - model = pybamm.lithium_ion.DFN() - parameter_values = model.default_parameter_values - parameter_values._replace_callable_function_parameters = False - sim = pybamm.Simulation(model, parameter_values=parameter_values) - sim.set_parameters() - pybamm.get_julia_mtk_model( - sim._model_with_set_params, geometry=sim.geometry, tspan=(0, 3600) - ) - - def test_exceptions(self): - model = pybamm.lithium_ion.SPM() - parameter_values = model.default_parameter_values - parameter_values._replace_callable_function_parameters = False - sim = pybamm.Simulation(model, parameter_values=parameter_values) - sim.set_parameters() - with self.assertRaisesRegex(ValueError, "must provide geometry"): - pybamm.get_julia_mtk_model( - sim._model_with_set_params, geometry=None, tspan=(0, 3600) - ) - with self.assertRaisesRegex(ValueError, "must provide tspan"): - pybamm.get_julia_mtk_model( - sim._model_with_set_params, geometry=sim.geometry, tspan=None - ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() From 09d7bda8612f3caeb591f35dd13f7e6dac278202 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 11:05:04 -0400 Subject: [PATCH 108/163] get rid of multimethod --- .../operations/evaluate_julia.py | 489 ++++++++---------- 1 file changed, 202 insertions(+), 287 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 15f6b8df13..95eb596403 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -6,7 +6,6 @@ import numpy import scipy from collections import OrderedDict -from multimethod import multimethod from math import floor import graphlib @@ -150,159 +149,192 @@ def break_down_concatenation(self, symbol): # They need to find their shapes, assuming that the shapes of # the nodes one level below them in the expression tree have # already been computed. - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.NumpyConcatenation): - my_id = symbol.id - children_julia, shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaNumpyConcatenation( - my_id, shape, children_julia - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.SparseStack): - my_id = symbol.id - children_julia, shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaSparseStack(my_id, shape, children_julia) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.DomainConcatenation): - my_id = symbol.id - children_julia, shape = self.break_down_concatenation(symbol) - self._intermediate[my_id] = JuliaDomainConcatenation( - my_id, - shape, - children_julia, - symbol.secondary_dimensions_npts, - symbol._children_slices, - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.MatrixMultiplication): - # Break down the binary tree - id_left, id_right, my_id = self.break_down_binary(symbol) - left_shape = self._intermediate[id_left].shape - right_shape = self._intermediate[id_right].shape - my_shape = (left_shape[0], right_shape[1]) - # Cache the result. - self._intermediate[my_id] = JuliaMatrixMultiplication( - id_left, id_right, my_id, my_shape - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Multiplication): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaMultiplication( - id_left, id_right, my_id, my_shape, "*" - ) - return my_id - - # Apparently an inner product is a hadamard product in pybamm - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Inner): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaMultiplication( - id_left, id_right, my_id, my_shape, "*" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Division): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaDivision( - id_left, id_right, my_id, my_shape, "/" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Addition): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaAddition( - id_left, id_right, my_id, my_shape, "+" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Subtraction): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaSubtraction( - id_left, id_right, my_id, my_shape, "-" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Minimum): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaMinMax( - id_left, id_right, my_id, my_shape, "min" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Maximum): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaMinMax( - id_left, id_right, my_id, my_shape, "max" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Power): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaPower(id_left, id_right, my_id, my_shape, "^") - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.EqualHeaviside): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaBitwiseBinaryOperation( - id_left, id_right, my_id, my_shape, "<=" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.NotEqualHeaviside): - id_left, id_right, my_id = self.break_down_binary(symbol) - my_shape = self.find_broadcastable_shape(id_left, id_right) - self._intermediate[my_id] = JuliaBitwiseBinaryOperation( - id_left, id_right, my_id, my_shape, "<" - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Index): - assert len(symbol.children) == 1 - id_lower = self._convert_tree_to_intermediate(symbol.children[0]) - child_shape = self._intermediate[id_lower].shape - child_ncols = child_shape[1] - - my_id = symbol.id - index = symbol.index - if type(index) is slice: - if index.step is None: - shape = ((index.stop) - (index.start), child_ncols) - elif type(index.step) == int: - shape = (floor((index.stop - index.start) / index.step), child_ncols) + def _convert_tree_to_intermediate(self, symbol): + if isinstance(symbol, pybamm.NumpyConcatenation): + my_id = symbol.id + children_julia, shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaNumpyConcatenation( + my_id, shape, children_julia + ) + elif isinstance(symbol, pybamm.SparseStack): + my_id = symbol.id + children_julia, shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaSparseStack(my_id, shape, children_julia) + elif isinstance(symbol, pybamm.DomainConcatenation): + my_id = symbol.id + children_julia, shape = self.break_down_concatenation(symbol) + self._intermediate[my_id] = JuliaDomainConcatenation( + my_id, + shape, + children_julia, + symbol.secondary_dimensions_npts, + symbol._children_slices, + ) + elif isinstance(symbol, pybamm.MatrixMultiplication): + # Break down the binary tree + id_left, id_right, my_id = self.break_down_binary(symbol) + left_shape = self._intermediate[id_left].shape + right_shape = self._intermediate[id_right].shape + my_shape = (left_shape[0], right_shape[1]) + # Cache the result. + self._intermediate[my_id] = JuliaMatrixMultiplication( + id_left, id_right, my_id, my_shape + ) + elif isinstance(symbol, pybamm.Multiplication) or isinstance( + symbol, pybamm.Inner + ): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaMultiplication( + id_left, id_right, my_id, my_shape, "*" + ) + elif isinstance(symbol, pybamm.Division): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaDivision( + id_left, id_right, my_id, my_shape, "/" + ) + elif isinstance(symbol, pybamm.Addition): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaAddition( + id_left, id_right, my_id, my_shape, "+" + ) + elif isinstance(symbol, pybamm.Subtraction): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaSubtraction( + id_left, id_right, my_id, my_shape, "-" + ) + elif isinstance(symbol, pybamm.Minimum): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaMinMax( + id_left, id_right, my_id, my_shape, "min" + ) + elif isinstance(symbol, pybamm.Maximum): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaMinMax( + id_left, id_right, my_id, my_shape, "max" + ) + elif isinstance(symbol, pybamm.Power): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaPower( + id_left, id_right, my_id, my_shape, "^" + ) + elif isinstance(symbol, pybamm.EqualHeaviside): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation( + id_left, id_right, my_id, my_shape, "<=" + ) + elif isinstance(symbol, pybamm.NotEqualHeaviside): + id_left, id_right, my_id = self.break_down_binary(symbol) + my_shape = self.find_broadcastable_shape(id_left, id_right) + self._intermediate[my_id] = JuliaBitwiseBinaryOperation( + id_left, id_right, my_id, my_shape, "<" + ) + elif isinstance(symbol, pybamm.Index): + assert len(symbol.children) == 1 + id_lower = self._convert_tree_to_intermediate(symbol.children[0]) + child_shape = self._intermediate[id_lower].shape + child_ncols = child_shape[1] + + my_id = symbol.id + index = symbol.index + if type(index) is slice: + if index.step is None: + shape = ((index.stop) - (index.start), child_ncols) + elif type(index.step) == int: + shape = ( + floor((index.stop - index.start) / index.step), + child_ncols, + ) + else: + raise NotImplementedError("index must be slice or int") + elif type(index) is int: + shape = (1, child_ncols) else: raise NotImplementedError("index must be slice or int") - elif type(index) is int: - shape = (1, child_ncols) - else: - raise NotImplementedError("index must be slice or int") - self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index, shape) + self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index, shape) + elif isinstance(symbol, pybamm.Min) or isinstance(symbol, pybamm.Max): + my_jl_name = symbol.julia_name + assert len(symbol.children) == 1 + my_shape = (1, 1) + input = self._convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaMinimumMaximum( + my_jl_name, input, my_id, my_shape + ) + elif isinstance(symbol, pybamm.Function): + my_jl_name = symbol.julia_name + assert len(symbol.children) == 1 + my_shape = symbol.children[0].shape + input = self._convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaBroadcastableFunction( + my_jl_name, input, my_id, my_shape + ) + elif isinstance(symbol, pybamm.Negate): + my_jl_name = "-" + assert len(symbol.children) == 1 + my_shape = symbol.children[0].shape + input = self._convert_tree_to_intermediate(symbol.children[0]) + my_id = symbol.id + self._intermediate[my_id] = JuliaNegation( + my_jl_name, input, my_id, my_shape + ) + elif isinstance(symbol, pybamm.Matrix): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + if value.shape == (1, 1): + self._intermediate[my_id] = JuliaScalar(my_id, value) + else: + self._intermediate[my_id] = JuliaConstant(my_id, value) + elif isinstance(symbol, pybamm.Vector): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + if value.shape == (1, 1): + self._intermediate[my_id] = JuliaScalar(my_id, value) + else: + self._intermediate[my_id] = JuliaConstant(my_id, value) + elif isinstance(symbol, pybamm.Scalar): + assert is_constant_and_can_evaluate(symbol) + my_id = symbol.id + value = symbol.evaluate() + self._intermediate[my_id] = JuliaScalar(my_id, value) + elif isinstance(symbol, pybamm.Time): + my_id = symbol.id + self._intermediate[my_id] = JuliaTime(my_id) + elif isinstance(symbol, pybamm.InputParameter): + my_id = symbol.id + name = symbol.name + self._intermediate[my_id] = JuliaInput(my_id, name) + elif isinstance(symbol, pybamm.StateVector): + my_id = symbol.id + first_point = symbol.first_point + last_point = symbol.last_point + points = (first_point, last_point) + shape = symbol.shape + self._intermediate[my_id] = JuliaStateVector(my_id, points, shape) + elif isinstance(symbol, pybamm.StateVectorDot): + my_id = symbol.id + first_point = symbol.first_point + last_point = symbol.last_point + points = (first_point, last_point) + shape = symbol.shape + self._intermediate[my_id] = JuliaStateVectorDot(my_id, points, shape) + else: + raise NotImplementedError( + "Conversion to Julia not implemented for a symbol of type '{}'".format( + type(symbol) + ) + ) return my_id def find_broadcastable_shape(self, id_left, id_right): @@ -349,123 +381,6 @@ def same_shape(self, id_left, id_right): # input and output have the same shape. The hard part is that # we have to know which is which and pybamm doesn't differentiate # between the two. So, we have to do that with an if statement. - @multimethod - def _convert_tree_to_intermediate(self, symbol): - raise NotImplementedError( - "Conversion to Julia not implemented for a symbol of type '{}'".format( - type(symbol) - ) - ) - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Min): - my_jl_name = symbol.julia_name - assert len(symbol.children) == 1 - my_shape = (1, 1) - input = self._convert_tree_to_intermediate(symbol.children[0]) - my_id = symbol.id - self._intermediate[my_id] = JuliaMinimumMaximum( - my_jl_name, input, my_id, my_shape - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Max): - my_jl_name = symbol.julia_name - assert len(symbol.children) == 1 - my_shape = (1, 1) - input = self._convert_tree_to_intermediate(symbol.children[0]) - my_id = symbol.id - self._intermediate[my_id] = JuliaMinimumMaximum( - my_jl_name, input, my_id, my_shape - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Function): - my_jl_name = symbol.julia_name - assert len(symbol.children) == 1 - my_shape = symbol.children[0].shape - input = self._convert_tree_to_intermediate(symbol.children[0]) - my_id = symbol.id - self._intermediate[my_id] = JuliaBroadcastableFunction( - my_jl_name, input, my_id, my_shape - ) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Negate): - my_jl_name = "-" - assert len(symbol.children) == 1 - my_shape = symbol.children[0].shape - input = self._convert_tree_to_intermediate(symbol.children[0]) - my_id = symbol.id - self._intermediate[my_id] = JuliaNegation(my_jl_name, input, my_id, my_shape) - return my_id - - # Constants and Values. There are only 2 of these. They must know their own shapes. - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Matrix): - assert is_constant_and_can_evaluate(symbol) - my_id = symbol.id - value = symbol.evaluate() - if value.shape == (1, 1): - self._intermediate[my_id] = JuliaScalar(my_id, value) - else: - self._intermediate[my_id] = JuliaConstant(my_id, value) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Vector): - assert is_constant_and_can_evaluate(symbol) - my_id = symbol.id - value = symbol.evaluate() - if value.shape == (1, 1): - self._intermediate[my_id] = JuliaScalar(my_id, value) - else: - self._intermediate[my_id] = JuliaConstant(my_id, value) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Scalar): - assert is_constant_and_can_evaluate(symbol) - my_id = symbol.id - value = symbol.evaluate() - self._intermediate[my_id] = JuliaScalar(my_id, value) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.Time): - my_id = symbol.id - self._intermediate[my_id] = JuliaTime(my_id) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.InputParameter): - my_id = symbol.id - name = symbol.name - self._intermediate[my_id] = JuliaInput(my_id, name) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.StateVector): - my_id = symbol.id - first_point = symbol.first_point - last_point = symbol.last_point - points = (first_point, last_point) - shape = symbol.shape - self._intermediate[my_id] = JuliaStateVector(my_id, points, shape) - return my_id - - @multimethod - def _convert_tree_to_intermediate(self, symbol: pybamm.StateVectorDot): - my_id = symbol.id - first_point = symbol.first_point - last_point = symbol.last_point - points = (first_point, last_point) - shape = symbol.shape - self._intermediate[my_id] = JuliaStateVectorDot(my_id, points, shape) - return my_id # Cache and Const Creation def create_cache(self, symbol): @@ -549,26 +464,26 @@ def create_const(self, symbol): self._cache_and_const_string += const_line return 0 - @multimethod - def write_const(self, mat_value: numpy.ndarray): - return mat_value - - @multimethod - def write_const(self, value: scipy.sparse._csr.csr_matrix): - row, col, data = scipy.sparse.find(value) - m, n = value.shape - np.set_printoptions( - threshold=max(np.get_printoptions()["threshold"], len(row) + 10) - ) + def write_const(self, value): + if isinstance(value, numpy.ndarray): + val_string = value + elif isinstance(value, scipy.sparse._csr.csr_matrix): + row, col, data = scipy.sparse.find(value) + m, n = value.shape + np.set_printoptions( + threshold=max(np.get_printoptions()["threshold"], len(row) + 10) + ) - val_string = "sparse({}, {}, {}{}, {}, {})".format( - np.array2string(row + 1, separator=","), - np.array2string(col + 1, separator=","), - self._type, - np.array2string(data, separator=","), - m, - n, - ) + val_string = "sparse({}, {}, {}{}, {}, {})".format( + np.array2string(row + 1, separator=","), + np.array2string(col + 1, separator=","), + self._type, + np.array2string(data, separator=","), + m, + n, + ) + else: + raise NotImplementedError("attempted to write an unsupported const") return val_string def clear(self): From 76c07aa0211589d38fd72c0a40662d9e388bc49d Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 13:31:16 -0400 Subject: [PATCH 109/163] vectors a vectors a vector --- pybamm/expression_tree/operations/evaluate_julia.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 95eb596403..675007a782 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -299,10 +299,7 @@ def _convert_tree_to_intermediate(self, symbol): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() - if value.shape == (1, 1): - self._intermediate[my_id] = JuliaScalar(my_id, value) - else: - self._intermediate[my_id] = JuliaConstant(my_id, value) + self._intermediate[my_id] = JuliaConstant(my_id, value) elif isinstance(symbol, pybamm.Scalar): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id From 426af96190aaa65fa5b444ea5681988e6a40a8fe Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 13:42:40 -0400 Subject: [PATCH 110/163] vectors maybe actually not a vector --- pybamm/expression_tree/operations/evaluate_julia.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 675007a782..528c2fc5d2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -299,7 +299,11 @@ def _convert_tree_to_intermediate(self, symbol): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() - self._intermediate[my_id] = JuliaConstant(my_id, value) + if value.shape == (1, 1): + value = value.toarray() + self._intermediate[my_id] = JuliaScalar(my_id, value) + else: + self._intermediate[my_id] = JuliaConstant(my_id, value) elif isinstance(symbol, pybamm.Scalar): assert is_constant_and_can_evaluate(symbol) my_id = symbol.id From 156d483c80b57dbe55ced14a95d8abb85930648b Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 13:46:14 -0400 Subject: [PATCH 111/163] maybe some vectors are vectors and some arent --- pybamm/expression_tree/operations/evaluate_julia.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 528c2fc5d2..aec185ece0 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -300,7 +300,8 @@ def _convert_tree_to_intermediate(self, symbol): my_id = symbol.id value = symbol.evaluate() if value.shape == (1, 1): - value = value.toarray() + if isinstance(value, scipy.sparse._csr.csr_matrix): + value = value.toarray() self._intermediate[my_id] = JuliaScalar(my_id, value) else: self._intermediate[my_id] = JuliaConstant(my_id, value) From 3f52a67bc6ba283f634a5b0c5fdefe4d09bc4550 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 19:19:43 -0400 Subject: [PATCH 112/163] cleanup and revert to ics_string --- pybamm/models/base_model.py | 13 +- requirements.txt | 1 + test_parallel.jl | 1466 ----------------- test_parallel.py | 34 - tests/unit/test_models/test_base_model.py | 10 +- .../test_base_model_generate_julia_diffeq.py | 24 +- 6 files changed, 22 insertions(+), 1526 deletions(-) delete mode 100644 test_parallel.jl delete mode 100644 test_parallel.py diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 32bd244a0e..f73f82a960 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1174,6 +1174,15 @@ def generate_julia_diffeq( get_consistent_ics_solver.set_up(self) get_consistent_ics_solver._set_initial_conditions(self, {}, False) ics = pybamm.Vector(self.y0.full()) + ics_converter = pybamm.JuliaConverter( + input_parameter_order=input_parameter_order, + cache_type=cache_type, + inline=inline, + preallocate=preallocate, + ) + ics_converter.convert_tree_to_intermediate(self.concatenated_initial_conditions) + ics_str = ics_converter.build_julia_code(funcname=name + "_ics") + ics_str.replace("(du, u, p, t)", "(u, p)") if generate_jacobian: size_state = self.concatenated_initial_conditions.size @@ -1189,9 +1198,9 @@ def generate_julia_diffeq( ) jac_converter.convert_tree_to_intermediate(expr) jac_str = jac_converter.build_julia_code(funcname="jac_" + name) - return eqn_str, ics, jac_str + return eqn_str, ics_str, jac_str - return eqn_str, ics + return eqn_str, ics_str def latexify(self, filename=None, newline=True): # For docstring, see pybamm.expression_tree.operations.latexify.Latexify diff --git a/requirements.txt b/requirements.txt index e7be2718d9..2080ea47e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ scikit-fem >= 0.2.0 casadi >= 3.5.0 imageio>=2.9.0 juliacall >= 0.1.0 +juliapkg >= 0.1.9 jupyter # For example notebooks pybtex>=0.24.0 sympy >= 1.8 diff --git a/test_parallel.jl b/test_parallel.jl deleted file mode 100644 index f14b9b6c7f..0000000000 --- a/test_parallel.jl +++ /dev/null @@ -1,1466 +0,0 @@ -begin -f = let -cache_1 = zeros(400) -const_1 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, - 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, - 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, - 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, - 37, 37, 38, 38, 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, - 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54, - 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 63, - 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, - 73, 73, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 79, 79, 80, 80, 81, 81, - 82, 82, 83, 83, 84, 84, 85, 85, 86, 86, 87, 87, 88, 88, 89, 89, 90, 90, - 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 99, - 100,100,101,101,102,102,103,103,104,104,105,105,106,106,107,107,108,108, - 109,109,110,110,111,111,112,112,113,113,114,114,115,115,116,116,117,117, - 118,118,119,119,120,120,121,121,122,122,123,123,124,124,125,125,126,126, - 127,127,128,128,129,129,130,130,131,131,132,132,133,133,134,134,135,135, - 136,136,137,137,138,138,139,139,140,140,141,141,142,142,143,143,144,144, - 145,145,146,146,147,147,148,148,149,149,150,150,151,151,152,152,153,153, - 154,154,155,155,156,156,157,157,158,158,159,159,160,160,161,161,162,162, - 163,163,164,164,165,165,166,166,167,167,168,168,169,169,170,170,171,171, - 172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179,180,180, - 181,181,182,182,183,183,184,184,185,185,186,186,187,187,188,188,189,189, - 190,190,191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198, - 199,199,200,200,201,201,202,202,203,203,204,204,205,205,206,206,207,207, - 208,208,209,209,210,210,211,211,212,212,213,213,214,214,215,215,216,216, - 217,217,218,218,219,219,220,220,221,221,222,222,223,223,224,224,225,225, - 226,226,227,227,228,228,229,229,230,230,231,231,232,232,233,233,234,234, - 235,235,236,236,237,237,238,238,239,239,240,240,241,241,242,242,243,243, - 244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,251,252,252, - 253,253,254,254,255,255,256,256,257,257,258,258,259,259,260,260,261,261, - 262,262,263,263,264,264,265,265,266,266,267,267,268,268,269,269,270,270, - 271,271,272,272,273,273,274,274,275,275,276,276,277,277,278,278,279,279, - 280,280,281,281,282,282,283,283,284,284,285,285,286,286,287,287,288,288, - 289,289,290,290,291,291,292,292,293,293,294,294,295,295,296,296,297,297, - 298,298,299,299,300,300,301,301,302,302,303,303,304,304,305,305,306,306, - 307,307,308,308,309,309,310,310,311,311,312,312,313,313,314,314,315,315, - 316,316,317,317,318,318,319,319,320,320,321,321,322,322,323,323,324,324, - 325,325,326,326,327,327,328,328,329,329,330,330,331,331,332,332,333,333, - 334,334,335,335,336,336,337,337,338,338,339,339,340,340,341,341,342,342, - 343,343,344,344,345,345,346,346,347,347,348,348,349,349,350,350,351,351, - 352,352,353,353,354,354,355,355,356,356,357,357,358,358,359,359,360,360, - 361,361,362,362,363,363,364,364,365,365,366,366,367,367,368,368,369,369, - 370,370,371,371,372,372,373,373,374,374,375,375,376,376,377,377,378,378, - 379,379,380,380,381,381,382,382,383,383,384,384,385,385,386,386,387,387, - 388,388,389,389,390,390,391,391,392,392,393,393,394,394,395,395,396,396, - 397,397,398,398,399,399,400,400], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, - 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, - 19, 20, 20, 21, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, - 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, - 38, 39, 39, 40, 40, 41, 41, 42, 43, 44, 44, 45, 45, 46, 46, 47, 47, 48, - 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, - 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 64, 65, 65, 66, 66, 67, - 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, - 76, 77, 77, 78, 78, 79, 79, 80, 80, 81, 81, 82, 82, 83, 83, 84, 85, 86, - 86, 87, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, - 95, 96, 96, 97, 97, 98, 98, 99, 99,100,100,101,101,102,102,103,103,104, - 104,105,106,107,107,108,108,109,109,110,110,111,111,112,112,113,113,114, - 114,115,115,116,116,117,117,118,118,119,119,120,120,121,121,122,122,123, - 123,124,124,125,125,126,127,128,128,129,129,130,130,131,131,132,132,133, - 133,134,134,135,135,136,136,137,137,138,138,139,139,140,140,141,141,142, - 142,143,143,144,144,145,145,146,146,147,148,149,149,150,150,151,151,152, - 152,153,153,154,154,155,155,156,156,157,157,158,158,159,159,160,160,161, - 161,162,162,163,163,164,164,165,165,166,166,167,167,168,169,170,170,171, - 171,172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179,180, - 180,181,181,182,182,183,183,184,184,185,185,186,186,187,187,188,188,189, - 190,191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198,199, - 199,200,200,201,201,202,202,203,203,204,204,205,205,206,206,207,207,208, - 208,209,209,210,211,212,212,213,213,214,214,215,215,216,216,217,217,218, - 218,219,219,220,220,221,221,222,222,223,223,224,224,225,225,226,226,227, - 227,228,228,229,229,230,230,231,232,233,233,234,234,235,235,236,236,237, - 237,238,238,239,239,240,240,241,241,242,242,243,243,244,244,245,245,246, - 246,247,247,248,248,249,249,250,250,251,251,252,253,254,254,255,255,256, - 256,257,257,258,258,259,259,260,260,261,261,262,262,263,263,264,264,265, - 265,266,266,267,267,268,268,269,269,270,270,271,271,272,272,273,274,275, - 275,276,276,277,277,278,278,279,279,280,280,281,281,282,282,283,283,284, - 284,285,285,286,286,287,287,288,288,289,289,290,290,291,291,292,292,293, - 293,294,295,296,296,297,297,298,298,299,299,300,300,301,301,302,302,303, - 303,304,304,305,305,306,306,307,307,308,308,309,309,310,310,311,311,312, - 312,313,313,314,314,315,316,317,317,318,318,319,319,320,320,321,321,322, - 322,323,323,324,324,325,325,326,326,327,327,328,328,329,329,330,330,331, - 331,332,332,333,333,334,334,335,335,336,337,338,338,339,339,340,340,341, - 341,342,342,343,343,344,344,345,345,346,346,347,347,348,348,349,349,350, - 350,351,351,352,352,353,353,354,354,355,355,356,356,357,358,359,359,360, - 360,361,361,362,362,363,363,364,364,365,365,366,366,367,367,368,368,369, - 369,370,370,371,371,372,372,373,373,374,374,375,375,376,376,377,377,378, - 379,380,380,381,381,382,382,383,383,384,384,385,385,386,386,387,387,388, - 388,389,389,390,390,391,391,392,392,393,393,394,394,395,395,396,396,397, - 397,398,398,399,400,401,401,402,402,403,403,404,404,405,405,406,406,407, - 407,408,408,409,409,410,410,411,411,412,412,413,413,414,414,415,415,416, - 416,417,417,418,418,419,419,420], Float64[-2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02, - -2.11522984e+05, 2.11522984e+05,-3.02175691e+04, 3.02175691e+04, - -1.11327886e+04, 1.11327886e+04,-5.71683739e+03, 5.71683739e+03, - -3.46758989e+03, 3.46758989e+03,-2.32442839e+03, 2.32442839e+03, - -1.66553530e+03, 1.66553530e+03,-1.25161529e+03, 1.25161529e+03, - -9.74760293e+02, 9.74760293e+02,-7.80527615e+02, 7.80527615e+02, - -6.39042246e+02, 6.39042246e+02,-5.32803485e+02, 5.32803485e+02, - -4.51008494e+02, 4.51008494e+02,-3.86696496e+02, 3.86696496e+02, - -3.35218674e+02, 3.35218674e+02,-2.93374457e+02, 2.93374457e+02, - -2.58902061e+02, 2.58902061e+02,-2.30166467e+02, 2.30166467e+02, - -2.05962009e+02, 2.05962009e+02,-1.85383859e+02, 1.85383859e+02], 400, 420) -cache_2 = zeros(420) -cache_3 = zeros(420) -const_2 = sparse([ 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, - 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, - 20, 20, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, - 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, - 40, 40, 41, 41, 44, 44, 45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, - 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, - 60, 60, 61, 61, 62, 62, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, - 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 79, 79, - 80, 80, 81, 81, 82, 82, 83, 83, 86, 86, 87, 87, 88, 88, 89, 89, 90, 90, - 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 99, - 100,100,101,101,102,102,103,103,104,104,107,107,108,108,109,109,110,110, - 111,111,112,112,113,113,114,114,115,115,116,116,117,117,118,118,119,119, - 120,120,121,121,122,122,123,123,124,124,125,125,128,128,129,129,130,130, - 131,131,132,132,133,133,134,134,135,135,136,136,137,137,138,138,139,139, - 140,140,141,141,142,142,143,143,144,144,145,145,146,146,149,149,150,150, - 151,151,152,152,153,153,154,154,155,155,156,156,157,157,158,158,159,159, - 160,160,161,161,162,162,163,163,164,164,165,165,166,166,167,167,170,170, - 171,171,172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179, - 180,180,181,181,182,182,183,183,184,184,185,185,186,186,187,187,188,188, - 191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198,199,199, - 200,200,201,201,202,202,203,203,204,204,205,205,206,206,207,207,208,208, - 209,209,212,212,213,213,214,214,215,215,216,216,217,217,218,218,219,219, - 220,220,221,221,222,222,223,223,224,224,225,225,226,226,227,227,228,228, - 229,229,230,230,233,233,234,234,235,235,236,236,237,237,238,238,239,239, - 240,240,241,241,242,242,243,243,244,244,245,245,246,246,247,247,248,248, - 249,249,250,250,251,251,254,254,255,255,256,256,257,257,258,258,259,259, - 260,260,261,261,262,262,263,263,264,264,265,265,266,266,267,267,268,268, - 269,269,270,270,271,271,272,272,275,275,276,276,277,277,278,278,279,279, - 280,280,281,281,282,282,283,283,284,284,285,285,286,286,287,287,288,288, - 289,289,290,290,291,291,292,292,293,293,296,296,297,297,298,298,299,299, - 300,300,301,301,302,302,303,303,304,304,305,305,306,306,307,307,308,308, - 309,309,310,310,311,311,312,312,313,313,314,314,317,317,318,318,319,319, - 320,320,321,321,322,322,323,323,324,324,325,325,326,326,327,327,328,328, - 329,329,330,330,331,331,332,332,333,333,334,334,335,335,338,338,339,339, - 340,340,341,341,342,342,343,343,344,344,345,345,346,346,347,347,348,348, - 349,349,350,350,351,351,352,352,353,353,354,354,355,355,356,356,359,359, - 360,360,361,361,362,362,363,363,364,364,365,365,366,366,367,367,368,368, - 369,369,370,370,371,371,372,372,373,373,374,374,375,375,376,376,377,377, - 380,380,381,381,382,382,383,383,384,384,385,385,386,386,387,387,388,388, - 389,389,390,390,391,391,392,392,393,393,394,394,395,395,396,396,397,397, - 398,398,401,401,402,402,403,403,404,404,405,405,406,406,407,407,408,408, - 409,409,410,410,411,411,412,412,413,413,414,414,415,415,416,416,417,417, - 418,418,419,419], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, - 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, - 19, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, - 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, - 38, 39, 39, 40, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47, 48, - 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, - 57, 58, 58, 59, 59, 60, 61, 62, 62, 63, 63, 64, 64, 65, 65, 66, 66, 67, - 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, - 76, 77, 77, 78, 78, 79, 79, 80, 81, 82, 82, 83, 83, 84, 84, 85, 85, 86, - 86, 87, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, - 95, 96, 96, 97, 97, 98, 98, 99, 99,100,101,102,102,103,103,104,104,105, - 105,106,106,107,107,108,108,109,109,110,110,111,111,112,112,113,113,114, - 114,115,115,116,116,117,117,118,118,119,119,120,121,122,122,123,123,124, - 124,125,125,126,126,127,127,128,128,129,129,130,130,131,131,132,132,133, - 133,134,134,135,135,136,136,137,137,138,138,139,139,140,141,142,142,143, - 143,144,144,145,145,146,146,147,147,148,148,149,149,150,150,151,151,152, - 152,153,153,154,154,155,155,156,156,157,157,158,158,159,159,160,161,162, - 162,163,163,164,164,165,165,166,166,167,167,168,168,169,169,170,170,171, - 171,172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179,180, - 181,182,182,183,183,184,184,185,185,186,186,187,187,188,188,189,189,190, - 190,191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198,199, - 199,200,201,202,202,203,203,204,204,205,205,206,206,207,207,208,208,209, - 209,210,210,211,211,212,212,213,213,214,214,215,215,216,216,217,217,218, - 218,219,219,220,221,222,222,223,223,224,224,225,225,226,226,227,227,228, - 228,229,229,230,230,231,231,232,232,233,233,234,234,235,235,236,236,237, - 237,238,238,239,239,240,241,242,242,243,243,244,244,245,245,246,246,247, - 247,248,248,249,249,250,250,251,251,252,252,253,253,254,254,255,255,256, - 256,257,257,258,258,259,259,260,261,262,262,263,263,264,264,265,265,266, - 266,267,267,268,268,269,269,270,270,271,271,272,272,273,273,274,274,275, - 275,276,276,277,277,278,278,279,279,280,281,282,282,283,283,284,284,285, - 285,286,286,287,287,288,288,289,289,290,290,291,291,292,292,293,293,294, - 294,295,295,296,296,297,297,298,298,299,299,300,301,302,302,303,303,304, - 304,305,305,306,306,307,307,308,308,309,309,310,310,311,311,312,312,313, - 313,314,314,315,315,316,316,317,317,318,318,319,319,320,321,322,322,323, - 323,324,324,325,325,326,326,327,327,328,328,329,329,330,330,331,331,332, - 332,333,333,334,334,335,335,336,336,337,337,338,338,339,339,340,341,342, - 342,343,343,344,344,345,345,346,346,347,347,348,348,349,349,350,350,351, - 351,352,352,353,353,354,354,355,355,356,356,357,357,358,358,359,359,360, - 361,362,362,363,363,364,364,365,365,366,366,367,367,368,368,369,369,370, - 370,371,371,372,372,373,373,374,374,375,375,376,376,377,377,378,378,379, - 379,380,381,382,382,383,383,384,384,385,385,386,386,387,387,388,388,389, - 389,390,390,391,391,392,392,393,393,394,394,395,395,396,396,397,397,398, - 398,399,399,400], Float64[ -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, - -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, - -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, - -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05], 420, 400) -cache_4 = zeros(420) -const_3 = sparse([ 21, 42, 63, 84,105,126,147,168,189,210,231,252,273,294,315,336,357,378, - 399,420], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20], Float64[-0.06303492,-0.06303492,-0.06303492,-0.06303492,-0.06303492,-0.06303492, - -0.06303492,-0.06303492,-0.06303492,-0.06303492,-0.06303492,-0.06303492, - -0.06303492,-0.06303492,-0.06303492,-0.06303492,-0.06303492,-0.06303492, - -0.06303492,-0.06303492], 420, 20) -cache_5 = zeros(20) -const_4 = [[3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375] - [3.375]] -cache_6 = zeros(20) -const_5 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20], [ 19, 20, 39, 40, 59, 60, 79, 80, 99,100,119,120,139,140,159,160,179,180, - 199,200,219,220,239,240,259,260,279,280,299,300,319,320,339,340,359,360, - 379,380,399,400], Float64[-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5, - -0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5, - -0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5], 20, 400) -cache_7 = zeros(400) -const_6 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, - 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, - 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, - 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, - 37, 37, 38, 38, 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, - 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54, - 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 63, - 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, - 73, 73, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 79, 79, 80, 80, 81, 81, - 82, 82, 83, 83, 84, 84, 85, 85, 86, 86, 87, 87, 88, 88, 89, 89, 90, 90, - 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 99, - 100,100,101,101,102,102,103,103,104,104,105,105,106,106,107,107,108,108, - 109,109,110,110,111,111,112,112,113,113,114,114,115,115,116,116,117,117, - 118,118,119,119,120,120,121,121,122,122,123,123,124,124,125,125,126,126, - 127,127,128,128,129,129,130,130,131,131,132,132,133,133,134,134,135,135, - 136,136,137,137,138,138,139,139,140,140,141,141,142,142,143,143,144,144, - 145,145,146,146,147,147,148,148,149,149,150,150,151,151,152,152,153,153, - 154,154,155,155,156,156,157,157,158,158,159,159,160,160,161,161,162,162, - 163,163,164,164,165,165,166,166,167,167,168,168,169,169,170,170,171,171, - 172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179,180,180, - 181,181,182,182,183,183,184,184,185,185,186,186,187,187,188,188,189,189, - 190,190,191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198, - 199,199,200,200,201,201,202,202,203,203,204,204,205,205,206,206,207,207, - 208,208,209,209,210,210,211,211,212,212,213,213,214,214,215,215,216,216, - 217,217,218,218,219,219,220,220,221,221,222,222,223,223,224,224,225,225, - 226,226,227,227,228,228,229,229,230,230,231,231,232,232,233,233,234,234, - 235,235,236,236,237,237,238,238,239,239,240,240,241,241,242,242,243,243, - 244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,251,252,252, - 253,253,254,254,255,255,256,256,257,257,258,258,259,259,260,260,261,261, - 262,262,263,263,264,264,265,265,266,266,267,267,268,268,269,269,270,270, - 271,271,272,272,273,273,274,274,275,275,276,276,277,277,278,278,279,279, - 280,280,281,281,282,282,283,283,284,284,285,285,286,286,287,287,288,288, - 289,289,290,290,291,291,292,292,293,293,294,294,295,295,296,296,297,297, - 298,298,299,299,300,300,301,301,302,302,303,303,304,304,305,305,306,306, - 307,307,308,308,309,309,310,310,311,311,312,312,313,313,314,314,315,315, - 316,316,317,317,318,318,319,319,320,320,321,321,322,322,323,323,324,324, - 325,325,326,326,327,327,328,328,329,329,330,330,331,331,332,332,333,333, - 334,334,335,335,336,336,337,337,338,338,339,339,340,340,341,341,342,342, - 343,343,344,344,345,345,346,346,347,347,348,348,349,349,350,350,351,351, - 352,352,353,353,354,354,355,355,356,356,357,357,358,358,359,359,360,360, - 361,361,362,362,363,363,364,364,365,365,366,366,367,367,368,368,369,369, - 370,370,371,371,372,372,373,373,374,374,375,375,376,376,377,377,378,378, - 379,379,380,380,381,381,382,382,383,383,384,384,385,385,386,386,387,387, - 388,388,389,389,390,390,391,391,392,392,393,393,394,394,395,395,396,396, - 397,397,398,398,399,399,400,400], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, - 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, - 19, 20, 20, 21, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, - 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, - 38, 39, 39, 40, 40, 41, 41, 42, 43, 44, 44, 45, 45, 46, 46, 47, 47, 48, - 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, - 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 64, 65, 65, 66, 66, 67, - 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, - 76, 77, 77, 78, 78, 79, 79, 80, 80, 81, 81, 82, 82, 83, 83, 84, 85, 86, - 86, 87, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, - 95, 96, 96, 97, 97, 98, 98, 99, 99,100,100,101,101,102,102,103,103,104, - 104,105,106,107,107,108,108,109,109,110,110,111,111,112,112,113,113,114, - 114,115,115,116,116,117,117,118,118,119,119,120,120,121,121,122,122,123, - 123,124,124,125,125,126,127,128,128,129,129,130,130,131,131,132,132,133, - 133,134,134,135,135,136,136,137,137,138,138,139,139,140,140,141,141,142, - 142,143,143,144,144,145,145,146,146,147,148,149,149,150,150,151,151,152, - 152,153,153,154,154,155,155,156,156,157,157,158,158,159,159,160,160,161, - 161,162,162,163,163,164,164,165,165,166,166,167,167,168,169,170,170,171, - 171,172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179,180, - 180,181,181,182,182,183,183,184,184,185,185,186,186,187,187,188,188,189, - 190,191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198,199, - 199,200,200,201,201,202,202,203,203,204,204,205,205,206,206,207,207,208, - 208,209,209,210,211,212,212,213,213,214,214,215,215,216,216,217,217,218, - 218,219,219,220,220,221,221,222,222,223,223,224,224,225,225,226,226,227, - 227,228,228,229,229,230,230,231,232,233,233,234,234,235,235,236,236,237, - 237,238,238,239,239,240,240,241,241,242,242,243,243,244,244,245,245,246, - 246,247,247,248,248,249,249,250,250,251,251,252,253,254,254,255,255,256, - 256,257,257,258,258,259,259,260,260,261,261,262,262,263,263,264,264,265, - 265,266,266,267,267,268,268,269,269,270,270,271,271,272,272,273,274,275, - 275,276,276,277,277,278,278,279,279,280,280,281,281,282,282,283,283,284, - 284,285,285,286,286,287,287,288,288,289,289,290,290,291,291,292,292,293, - 293,294,295,296,296,297,297,298,298,299,299,300,300,301,301,302,302,303, - 303,304,304,305,305,306,306,307,307,308,308,309,309,310,310,311,311,312, - 312,313,313,314,314,315,316,317,317,318,318,319,319,320,320,321,321,322, - 322,323,323,324,324,325,325,326,326,327,327,328,328,329,329,330,330,331, - 331,332,332,333,333,334,334,335,335,336,337,338,338,339,339,340,340,341, - 341,342,342,343,343,344,344,345,345,346,346,347,347,348,348,349,349,350, - 350,351,351,352,352,353,353,354,354,355,355,356,356,357,358,359,359,360, - 360,361,361,362,362,363,363,364,364,365,365,366,366,367,367,368,368,369, - 369,370,370,371,371,372,372,373,373,374,374,375,375,376,376,377,377,378, - 379,380,380,381,381,382,382,383,383,384,384,385,385,386,386,387,387,388, - 388,389,389,390,390,391,391,392,392,393,393,394,394,395,395,396,396,397, - 397,398,398,399,400,401,401,402,402,403,403,404,404,405,405,406,406,407, - 407,408,408,409,409,410,410,411,411,412,412,413,413,414,414,415,415,416, - 416,417,417,418,418,419,419,420], Float64[-5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02, - -5.42366624e+05, 5.42366624e+05,-7.74809464e+04, 7.74809464e+04, - -2.85456118e+04, 2.85456118e+04,-1.46585574e+04, 1.46585574e+04, - -8.89125614e+03, 8.89125614e+03,-5.96007280e+03, 5.96007280e+03, - -4.27060334e+03, 4.27060334e+03,-3.20926997e+03, 3.20926997e+03, - -2.49938537e+03, 2.49938537e+03,-2.00135286e+03, 2.00135286e+03, - -1.63856986e+03, 1.63856986e+03,-1.36616278e+03, 1.36616278e+03, - -1.15643204e+03, 1.15643204e+03,-9.91529478e+02, 9.91529478e+02, - -8.59535063e+02, 8.59535063e+02,-7.52242198e+02, 7.52242198e+02, - -6.63851438e+02, 6.63851438e+02,-5.90170429e+02, 5.90170429e+02, - -5.28107716e+02, 5.28107716e+02,-4.75343229e+02, 4.75343229e+02], 400, 420) -cache_8 = zeros(420) -cache_9 = zeros(420) -const_7 = sparse([ 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, - 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, - 20, 20, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, - 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, - 40, 40, 41, 41, 44, 44, 45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, - 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, - 60, 60, 61, 61, 62, 62, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, - 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 79, 79, - 80, 80, 81, 81, 82, 82, 83, 83, 86, 86, 87, 87, 88, 88, 89, 89, 90, 90, - 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 99, - 100,100,101,101,102,102,103,103,104,104,107,107,108,108,109,109,110,110, - 111,111,112,112,113,113,114,114,115,115,116,116,117,117,118,118,119,119, - 120,120,121,121,122,122,123,123,124,124,125,125,128,128,129,129,130,130, - 131,131,132,132,133,133,134,134,135,135,136,136,137,137,138,138,139,139, - 140,140,141,141,142,142,143,143,144,144,145,145,146,146,149,149,150,150, - 151,151,152,152,153,153,154,154,155,155,156,156,157,157,158,158,159,159, - 160,160,161,161,162,162,163,163,164,164,165,165,166,166,167,167,170,170, - 171,171,172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179, - 180,180,181,181,182,182,183,183,184,184,185,185,186,186,187,187,188,188, - 191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198,199,199, - 200,200,201,201,202,202,203,203,204,204,205,205,206,206,207,207,208,208, - 209,209,212,212,213,213,214,214,215,215,216,216,217,217,218,218,219,219, - 220,220,221,221,222,222,223,223,224,224,225,225,226,226,227,227,228,228, - 229,229,230,230,233,233,234,234,235,235,236,236,237,237,238,238,239,239, - 240,240,241,241,242,242,243,243,244,244,245,245,246,246,247,247,248,248, - 249,249,250,250,251,251,254,254,255,255,256,256,257,257,258,258,259,259, - 260,260,261,261,262,262,263,263,264,264,265,265,266,266,267,267,268,268, - 269,269,270,270,271,271,272,272,275,275,276,276,277,277,278,278,279,279, - 280,280,281,281,282,282,283,283,284,284,285,285,286,286,287,287,288,288, - 289,289,290,290,291,291,292,292,293,293,296,296,297,297,298,298,299,299, - 300,300,301,301,302,302,303,303,304,304,305,305,306,306,307,307,308,308, - 309,309,310,310,311,311,312,312,313,313,314,314,317,317,318,318,319,319, - 320,320,321,321,322,322,323,323,324,324,325,325,326,326,327,327,328,328, - 329,329,330,330,331,331,332,332,333,333,334,334,335,335,338,338,339,339, - 340,340,341,341,342,342,343,343,344,344,345,345,346,346,347,347,348,348, - 349,349,350,350,351,351,352,352,353,353,354,354,355,355,356,356,359,359, - 360,360,361,361,362,362,363,363,364,364,365,365,366,366,367,367,368,368, - 369,369,370,370,371,371,372,372,373,373,374,374,375,375,376,376,377,377, - 380,380,381,381,382,382,383,383,384,384,385,385,386,386,387,387,388,388, - 389,389,390,390,391,391,392,392,393,393,394,394,395,395,396,396,397,397, - 398,398,401,401,402,402,403,403,404,404,405,405,406,406,407,407,408,408, - 409,409,410,410,411,411,412,412,413,413,414,414,415,415,416,416,417,417, - 418,418,419,419], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, - 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, - 19, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, - 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, - 38, 39, 39, 40, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47, 48, - 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, - 57, 58, 58, 59, 59, 60, 61, 62, 62, 63, 63, 64, 64, 65, 65, 66, 66, 67, - 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, - 76, 77, 77, 78, 78, 79, 79, 80, 81, 82, 82, 83, 83, 84, 84, 85, 85, 86, - 86, 87, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, - 95, 96, 96, 97, 97, 98, 98, 99, 99,100,101,102,102,103,103,104,104,105, - 105,106,106,107,107,108,108,109,109,110,110,111,111,112,112,113,113,114, - 114,115,115,116,116,117,117,118,118,119,119,120,121,122,122,123,123,124, - 124,125,125,126,126,127,127,128,128,129,129,130,130,131,131,132,132,133, - 133,134,134,135,135,136,136,137,137,138,138,139,139,140,141,142,142,143, - 143,144,144,145,145,146,146,147,147,148,148,149,149,150,150,151,151,152, - 152,153,153,154,154,155,155,156,156,157,157,158,158,159,159,160,161,162, - 162,163,163,164,164,165,165,166,166,167,167,168,168,169,169,170,170,171, - 171,172,172,173,173,174,174,175,175,176,176,177,177,178,178,179,179,180, - 181,182,182,183,183,184,184,185,185,186,186,187,187,188,188,189,189,190, - 190,191,191,192,192,193,193,194,194,195,195,196,196,197,197,198,198,199, - 199,200,201,202,202,203,203,204,204,205,205,206,206,207,207,208,208,209, - 209,210,210,211,211,212,212,213,213,214,214,215,215,216,216,217,217,218, - 218,219,219,220,221,222,222,223,223,224,224,225,225,226,226,227,227,228, - 228,229,229,230,230,231,231,232,232,233,233,234,234,235,235,236,236,237, - 237,238,238,239,239,240,241,242,242,243,243,244,244,245,245,246,246,247, - 247,248,248,249,249,250,250,251,251,252,252,253,253,254,254,255,255,256, - 256,257,257,258,258,259,259,260,261,262,262,263,263,264,264,265,265,266, - 266,267,267,268,268,269,269,270,270,271,271,272,272,273,273,274,274,275, - 275,276,276,277,277,278,278,279,279,280,281,282,282,283,283,284,284,285, - 285,286,286,287,287,288,288,289,289,290,290,291,291,292,292,293,293,294, - 294,295,295,296,296,297,297,298,298,299,299,300,301,302,302,303,303,304, - 304,305,305,306,306,307,307,308,308,309,309,310,310,311,311,312,312,313, - 313,314,314,315,315,316,316,317,317,318,318,319,319,320,321,322,322,323, - 323,324,324,325,325,326,326,327,327,328,328,329,329,330,330,331,331,332, - 332,333,333,334,334,335,335,336,336,337,337,338,338,339,339,340,341,342, - 342,343,343,344,344,345,345,346,346,347,347,348,348,349,349,350,350,351, - 351,352,352,353,353,354,354,355,355,356,356,357,357,358,358,359,359,360, - 361,362,362,363,363,364,364,365,365,366,366,367,367,368,368,369,369,370, - 370,371,371,372,372,373,373,374,374,375,375,376,376,377,377,378,378,379, - 379,380,381,382,382,383,383,384,384,385,385,386,386,387,387,388,388,389, - 389,390,390,391,391,392,392,393,393,394,394,395,395,396,396,397,397,398, - 398,399,399,400], Float64[ -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, - -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, - -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, - -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, - -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , - -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25, - -12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, - -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , - -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, - -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 , - -14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , - -0.45, 0.45, -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, - -3.2 , 3.2 , -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , - -8.45, 8.45, -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45, - -16.2 , 16.2 ,-18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, - -0.8 , 0.8 , -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , - -4.05, 4.05, -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, - -9.8 , 9.8 ,-11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 , - -18.05, 18.05, -0.05, 0.05, -0.2 , 0.2 , -0.45, 0.45, -0.8 , 0.8 , - -1.25, 1.25, -1.8 , 1.8 , -2.45, 2.45, -3.2 , 3.2 , -4.05, 4.05, - -5. , 5. , -6.05, 6.05, -7.2 , 7.2 , -8.45, 8.45, -9.8 , 9.8 , - -11.25, 11.25,-12.8 , 12.8 ,-14.45, 14.45,-16.2 , 16.2 ,-18.05, 18.05], 420, 400) -cache_10 = zeros(420) -const_8 = sparse([ 21, 42, 63, 84,105,126,147,168,189,210,231,252,273,294,315,336,357,378, - 399,420], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20], Float64[-0.01438978,-0.01438978,-0.01438978,-0.01438978,-0.01438978,-0.01438978, - -0.01438978,-0.01438978,-0.01438978,-0.01438978,-0.01438978,-0.01438978, - -0.01438978,-0.01438978,-0.01438978,-0.01438978,-0.01438978,-0.01438978, - -0.01438978,-0.01438978], 420, 20) -cache_11 = zeros(20) -const_9 = [[2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125] - [2.8125]] -cache_12 = zeros(20) -const_10 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20], [ 19, 20, 39, 40, 59, 60, 79, 80, 99,100,119,120,139,140,159,160,179,180, - 199,200,219,220,239,240,259,260,279,280,299,300,319,320,339,340,359,360, - 379,380,399,400], Float64[-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5, - -0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5, - -0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5,-0.5, 1.5], 20, 400) -cache_13 = zeros(60) -const_11 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, - 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, - 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, - 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60,61], Float64[ 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 22399.53627739,-22399.53627739, 22399.53627739,-22399.53627739, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935, - 5599.88406935, -5599.88406935, 5599.88406935, -5599.88406935], 60, 61) -cache_14 = zeros(61) -cache_15 = zeros(61) -const_12 = sparse([ 1, 1,61,61], [ 1, 2,59,60], Float64[ 1.5,-0.5,-0.5, 1.5], 61, 60) -cache_16 = zeros(60) -const_13 = [[-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548]] -cache_17 = zeros(60) -cache_18 = zeros(61) -const_14 = sparse([ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, - 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, - 50,51,52,53,54,55,56,57,58,59,60], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 61, 59) -cache_19 = zeros(59) -cache_20 = zeros(59) -const_15 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) -cache_21 = zeros(59) -const_16 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], [ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, - 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, - 50,51,52,53,54,55,56,57,58,59,60], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) -cache_22 = zeros(59) -const_17 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, - 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, - 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, - 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.2,0.8,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.8,0.2,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5], 59, 60) -cache_23 = zeros(61) -const_18 = sparse([ 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13,13, - 14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25, - 26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37, - 38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49,49, - 50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[ -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -72., 72.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180., -72., 72., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45.], 61, 60) -cache_24 = zeros(60) -cache_25 = zeros(61) -const_19 = sparse([ 1, 2, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, - 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, - 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, - 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,61, - 60,61], [ 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12, - 12,13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24, - 24,25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36, - 36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48, - 48,49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,59, - 60,60], Float64[ 1.5, 0.5,-0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,-0.5, 0.5, 1.5], 61, 60) -cache_26 = zeros(60) -const_20 = [[0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [4.75788502] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029] - [0.78180029]] -cache_27 = zeros(61) -const_21 = sparse([ 1, 1,61,61], [ 1, 2,59,60], Float64[ 1.5,-0.5,-0.5, 1.5], 61, 60) -cache_28 = zeros(60) -cache_29 = zeros(61) -const_22 = sparse([ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, - 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, - 50,51,52,53,54,55,56,57,58,59,60], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 61, 59) -cache_30 = zeros(59) -cache_31 = zeros(59) -const_23 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) -cache_32 = zeros(59) -const_24 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], [ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, - 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, - 50,51,52,53,54,55,56,57,58,59,60], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) -cache_33 = zeros(59) -const_25 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, - 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, - 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, - 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.2,0.8,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.8,0.2,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5], 59, 60) -cache_34 = zeros(61) -const_26 = sparse([ 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13,13, - 14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25, - 26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37, - 38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49,49, - 50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[ -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -72., 72.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180., -72., 72., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45.], 61, 60) -cache_35 = zeros(60) -const_27 = [[0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.]] -cache_36 = zeros(20) -const_28 = sparse([ 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, - 9,10, 9,10,11,10,11,12,11,12,13,12,13,14,13,14,15,14,15,16,15,16,17,16, - 17,18,17,18,19,18,19,20,19,20], [ 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, - 9, 9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16,16,16,17, - 17,17,18,18,18,19,19,19,20,20], Float64[-1343343.56929193, 447781.18976398, 447781.18976398, -895562.37952795, - 447781.18976398, 447781.18976398, -895562.37952795, 447781.18976398, - 447781.18976398, -895562.37952795, 447781.18976398, 447781.18976398, - -895562.37952795, 447781.18976398, 447781.18976398, -895562.37952796, - 447781.18976398, 447781.18976398, -895562.37952795, 447781.18976398, - 447781.18976398, -895562.37952795, 447781.18976398, 447781.18976398, - -895562.37952795, 447781.18976398, 447781.18976398, -895562.37952795, - 447781.18976398, 447781.18976398, -895562.37952796, 447781.18976398, - 447781.18976398, -895562.37952796, 447781.18976398, 447781.18976398, - -895562.37952795, 447781.18976398, 447781.18976398, -895562.37952796, - 447781.18976398, 447781.18976398, -895562.37952795, 447781.18976398, - 447781.18976398, -895562.37952796, 447781.18976398, 447781.18976398, - -895562.37952796, 447781.18976398, 447781.18976398, -895562.37952795, - 447781.18976398, 447781.18976398, -895562.37952796, 447781.18976398, - 447781.18976398, -447781.18976398], 20, 20) -cache_37 = zeros(20) -const_29 = sparse([ 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, - 9,10, 9,10,11,10,11,12,11,12,13,12,13,14,13,14,15,14,15,16,15,16,17,16, - 17,18,17,18,19,18,19,20,19,20], [ 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, - 9, 9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16,16,16,17, - 17,17,18,18,18,19,19,19,20,20], Float64[-34063.86923059, 34063.86923059, 34063.86923059,-68127.73846118, - 34063.86923059, 34063.86923059,-68127.73846118, 34063.86923059, - 34063.86923059,-68127.73846118, 34063.86923059, 34063.86923059, - -68127.73846118, 34063.86923059, 34063.86923059,-68127.73846118, - 34063.86923059, 34063.86923059,-68127.73846118, 34063.86923059, - 34063.86923059,-68127.73846118, 34063.86923059, 34063.86923059, - -68127.73846118, 34063.86923059, 34063.86923059,-68127.73846118, - 34063.86923059, 34063.86923059,-68127.73846118, 34063.86923059, - 34063.86923059,-68127.73846118, 34063.86923059, 34063.86923059, - -68127.73846118, 34063.86923059, 34063.86923059,-68127.73846118, - 34063.86923059, 34063.86923059,-68127.73846118, 34063.86923059, - 34063.86923059,-68127.73846118, 34063.86923059, 34063.86923059, - -68127.73846118, 34063.86923059, 34063.86923059,-68127.73846118, - 34063.86923059, 34063.86923059,-68127.73846118, 34063.86923059, - 34063.86923059,-34063.86923059], 20, 20) -const_30 = [[ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [-45.]] -cache_38 = zeros(60) -const_31 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, - 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, - 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, - 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60,61], Float64[ -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45.], 60, 61) -cache_39 = zeros(61) -cache_40 = zeros(60) -const_32 = [[0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.] - [0.]] - -function f_with_consts(dy, y, p, t) - -mul!(cache_3,const_2,(@view y[2:401])) -mul!(cache_6,const_5,(@view y[2:401])) -@. cache_5 = (const_4 * (((0.0006324555320336759 * (max(((@view y[802:821]) / 0.3),1e-08) ^ 0.5)) * ((max(min(cache_6,0.99999999),1e-08) ^ 0.5) * 158.06094392304414)) * ((24983.2619938437 - (max(min(cache_6,0.99999999),1e-08) * 24983.2619938437)) ^ 0.5))) * (sinh((0.5 * (((@view y[862:881]) - (@view y[902:921])) - ((((((((((((0.194 + (1.5 * (exp((-120.0 * max(min(cache_6,0.9999999999),1e-10)))))) + (0.0351 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.286) / 0.083))))) - (0.0045 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.849) / 0.119))))) - (0.035 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.9233) / 0.05))))) - (0.0147 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.5) / 0.034))))) - (0.102 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.194) / 0.142))))) - (0.022 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.9) / 0.0164))))) - (0.011 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.124) / 0.0226))))) + (0.0155 * (tanh(((max(min(cache_6,0.9999999999),1e-10) - 0.105) / 0.029))))) + ((1e-06 * (1.0 / max(min(cache_6,0.9999999999),1e-10))) + (1e-06 * (1.0 / (max(min(cache_6,0.9999999999),1e-10) - 1.0))))) - 0.175189434028335) / 0.025692579121493725))))) -mul!(cache_4,const_3,cache_5) -@. cache_2 = cache_3 + cache_4 -mul!(cache_1,const_1,cache_2) -mul!(cache_9,const_7,(@view y[402:801])) -mul!(cache_12,const_10,(@view y[402:801])) -@. cache_11 = (const_9 * (((1.8973665961010275e-05 * (max(((@view y[842:861]) / 0.3),1e-08) ^ 0.5)) * ((max(min(cache_12,0.99999999),1e-08) ^ 0.5) * 226.313777156689)) * ((51217.9257309275 - (max(min(cache_12,0.99999999),1e-08) * 51217.9257309275)) ^ 0.5))) * (sinh((0.5 * (((@view y[882:901]) - (@view y[942:961])) - (((((((((2.16216 + (0.07645 * (tanh((30.834 - (57.858397200000006 * max(min(cache_12,0.9999999999),1e-10))))))) + (2.1581 * (tanh((52.294 - (53.412228 * max(min(cache_12,0.9999999999),1e-10))))))) - (0.14169 * (tanh((11.0923 - (21.0852666 * max(min(cache_12,0.9999999999),1e-10))))))) + (0.2051 * (tanh((1.4684 - (5.829105600000001 * max(min(cache_12,0.9999999999),1e-10))))))) + (0.2531 * (tanh((((- (1.062 * max(min(cache_12,0.9999999999),1e-10))) + 0.56478) / 0.1316))))) - (0.02167 * (tanh((((1.062 * max(min(cache_12,0.9999999999),1e-10)) - 0.525) / 0.006))))) + ((1e-06 * (1.0 / max(min(cache_12,0.9999999999),1e-10))) + (1e-06 * (1.0 / (max(min(cache_12,0.9999999999),1e-10) - 1.0))))) - 4.027013014008729) / 0.025692579121493725))))) -mul!(cache_10,const_8,cache_11) -@. cache_8 = cache_9 + cache_10 -mul!(cache_7,const_6,cache_8) -@. cache_17[1:20] = ((@view y[802:821]) * 3333.3333333333335) -@. cache_17[21:40] = ((@view y[822:841]) * 1000.0) -@. cache_17[41:60] = ((@view y[842:861]) * 3333.3333333333335) -@. cache_16 = const_13 * (exp((-0.00065 * max(cache_17,10.0)))) -mul!(cache_15,const_12,cache_16) -mul!(cache_20,const_15,cache_16) -mul!(cache_21,const_16,cache_16) -mul!(cache_22,const_17,cache_16) -@. cache_19 = (cache_20 * cache_21) / (cache_22 + 1e-16) -mul!(cache_18,const_14,cache_19) -@. cache_24[1:20] = ((@view y[802:821]) / 0.3) -@. cache_24[21:40] = (@view y[822:841]) -@. cache_24[41:60] = ((@view y[842:861]) / 0.3) -mul!(cache_23,const_18,cache_24) -@. cache_26 = (((0.0911 + (0.0019100999999999999 * max(cache_17,10.0))) - (1.052e-06 * (max(cache_17,10.0) ^ 2.0))) + (1.554e-10 * (max(cache_17,10.0) ^ 3.0))) * const_20 -mul!(cache_25,const_19,cache_26) -@. cache_28 = 1.2 / max(cache_24,0.01) -mul!(cache_27,const_21,cache_28) -mul!(cache_31,const_23,cache_28) -mul!(cache_32,const_24,cache_28) -mul!(cache_33,const_25,cache_28) -@. cache_30 = (cache_31 * cache_32) / (cache_33 + 1e-16) -mul!(cache_29,const_22,cache_30) -mul!(cache_34,const_26,(@view y[902:961])) -@. cache_14 = ((cache_15 + cache_18) * cache_23) + (0.08030500458941565 * ((cache_25 * ((cache_27 + cache_29) * cache_23)) - (cache_25 * cache_34))) -mul!(cache_13,const_11,cache_14) -@. cache_35[1:20] = (cache_5 / 0.04002679875215723) -@. cache_35[21:40] = const_27 -@. cache_35[41:60] = (cache_11 / 0.04002679875215723) -mul!(cache_36,const_28,(@view y[862:881])) -mul!(cache_37,const_29,(@view y[882:901])) -@. cache_39 = (cache_25 * ((cache_27 + cache_29) * cache_23)) - (cache_25 * cache_34) -mul!(cache_38,const_31,cache_39) -@. cache_40[1:20] = cache_5 -@. cache_40[21:40] = const_32 -@. cache_40[41:60] = cache_11 -@. dy[1:1] = 4.27249308415467 -@. dy[2:401] = cache_1 -@. dy[402:801] = cache_7 -@. dy[802:861] = (cache_13 + cache_35) -@. dy[862:881] = ((- cache_36) + cache_5) -@. dy[882:901] = ((- (cache_37 + const_30)) + cache_11) -@. dy[902:961] = (cache_38 - cache_40) - - return nothing -end -end -end \ No newline at end of file diff --git a/test_parallel.py b/test_parallel.py deleted file mode 100644 index ca5bf82f98..0000000000 --- a/test_parallel.py +++ /dev/null @@ -1,34 +0,0 @@ -import pybamm -import numpy as np -import copy -import re - - - -model = pybamm.lithium_ion.DFN() -sim = pybamm.Simulation(model) -sim.build() - -expr = pybamm.numpy_concatenation(sim.built_model.concatenated_rhs, sim.built_model.concatenated_algebraic) - -converter = pybamm.JuliaConverter(parallel=False, inline=True) -converter._convert_tree_to_intermediate(expr) -my_jl_str = converter.build_julia_code(topcut_options={"race heuristic": "search_and_sync"}) - - -text_file = open("test_parallel.jl", "w") -text_file.write(my_jl_str) -text_file.close() - - - - - - - - - - - - - diff --git a/tests/unit/test_models/test_base_model.py b/tests/unit/test_models/test_base_model.py index 2a541b7d68..058009bf6b 100644 --- a/tests/unit/test_models/test_base_model.py +++ b/tests/unit/test_models/test_base_model.py @@ -611,9 +611,7 @@ def test_generate_julia_diffeq(self): rhs_str, ics_str = model.generate_julia_diffeq() self.assertIsInstance(rhs_str, str) self.assertIn("ode_test_model", rhs_str) - self.assertIsInstance(ics_str, str) - self.assertIn("ode_test_model_u0", ics_str) - self.assertIn("(u0, p)", ics_str) + self.assertIsInstance(ics_str, pybamm.Vector) # ODE model with input parameters model = pybamm.BaseModel(name="ode test model 2") @@ -628,11 +626,9 @@ def test_generate_julia_diffeq(self): rhs_str, ics_str = model.generate_julia_diffeq(input_parameter_order=["p", "q"]) self.assertIsInstance(rhs_str, str) self.assertIn("ode_test_model_2", rhs_str) - self.assertIn("p, q = p", rhs_str) + self.assertIn("p,q,= p", rhs_str) - self.assertIsInstance(ics_str, str) - self.assertIn("ode_test_model_2_u0", ics_str) - self.assertIn("p, q = p", ics_str) + self.assertIsInstance(ics_str, pybamm.Vector) def test_set_initial_conditions(self): # Set up model diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index a3a021a2af..bb173cea04 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -4,6 +4,7 @@ import platform import unittest import pybamm +import numpy as np have_julia = True # pybamm.have_julia() if have_julia and platform.system() != "Windows": @@ -29,8 +30,6 @@ def test_generate_ode(self): self.assertIn("ode_test_model", rhs_str) self.assertIn("(dy, y, p, t)", rhs_str) self.assertIsInstance(ics_str, pybamm.Vector) - self.assertIn("ode_test_model_u0", ics_str) - self.assertIn("(u0, p)", ics_str) # ODE model with input parameters model = pybamm.BaseModel(name="ode test model 2") @@ -45,11 +44,9 @@ def test_generate_ode(self): rhs_str, ics_str = model.generate_julia_diffeq(input_parameter_order=["p", "q"]) self.assertIsInstance(rhs_str, str) self.assertIn("ode_test_model_2", rhs_str) - self.assertIn("p, q = p", rhs_str) + self.assertIn("p,q,= p", rhs_str) - self.assertIsInstance(ics_str, str) - self.assertIn("ode_test_model_2_u0", ics_str) - self.assertIn("p, q = p", ics_str) + self.assertIsInstance(ics_str, pybamm.Vector) def test_generate_dae(self): # ODE model with no input parameters @@ -65,26 +62,21 @@ def test_generate_dae(self): self.assertIsInstance(eqn_str, str) self.assertIn("dae_test_model", eqn_str) self.assertIn("(dy, y, p, t)", eqn_str) - self.assertIsInstance(ics_str, str) - self.assertIn("dae_test_model_u0", ics_str) - self.assertIn("(u0, p)", ics_str) - self.assertIn("[1.,2.]", ics_str) + self.assertIsInstance(ics_str, pybamm.Vector) # Generate eqn and ics for the Julia model (implicit) eqn_str, ics_str = model.generate_julia_diffeq(dae_type="implicit") self.assertIsInstance(eqn_str, str) self.assertIn("dae_test_model", eqn_str) self.assertIn("(out, dy, y, p, t)", eqn_str) - self.assertIsInstance(ics_str, str) - self.assertIn("dae_test_model_u0", ics_str) - self.assertIn("(u0, p)", ics_str) + self.assertIsInstance(ics_str, pybamm.Vector) # Calculate initial conditions in python eqn_str, ics_str = model.generate_julia_diffeq( get_consistent_ics_solver=pybamm.CasadiSolver() ) # Check that the initial conditions are consistent - self.assertIn("[1.,1.]", ics_str) + self.assertEqual(pybamm.Vector(np.array([1., 1.])), ics_str) def test_generate_pde(self): # ODE model with no input parameters @@ -113,9 +105,7 @@ def test_generate_pde(self): self.assertIsInstance(rhs_str, str) self.assertIn("pde_test_model", rhs_str) self.assertIn("(dy, y, p, t)", rhs_str) - self.assertIsInstance(ics_str, str) - self.assertIn("pde_test_model_u0", ics_str) - self.assertIn("(u0, p)", ics_str) + self.assertIsInstance(ics_str, pybamm.Vector) if __name__ == "__main__": From f7bab2e6888528dde06a1197a38bdb825aabb234 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 20:32:42 -0400 Subject: [PATCH 113/163] ics --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index f73f82a960..681cdde26e 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1180,7 +1180,7 @@ def generate_julia_diffeq( inline=inline, preallocate=preallocate, ) - ics_converter.convert_tree_to_intermediate(self.concatenated_initial_conditions) + ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name + "_ics") ics_str.replace("(du, u, p, t)", "(u, p)") From 0eb20eddbdfac78523f697b3a1ca621f291c088e Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 21:05:11 -0400 Subject: [PATCH 114/163] revert ics --- pybamm/expression_tree/operations/evaluate_julia.py | 13 +++++++------ pybamm/models/base_model.py | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index aec185ece0..1f1ab8a522 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -535,12 +535,13 @@ def write_function_easy(self, funcname, inline=True): self._cache_and_const_string = ( "begin\n{} = let \n".format(funcname) + self._cache_and_const_string ) - self._cache_and_const_string = remove_lines_with( - self._cache_and_const_string, top_var_name - ) - self._cache_initialization_string = remove_lines_with( - self._cache_initialization_string, top_var_name - ) + if len(self._intermediate)>1: + self._cache_and_const_string = remove_lines_with( + self._cache_and_const_string, top_var_name + ) + self._cache_initialization_string = remove_lines_with( + self._cache_initialization_string, top_var_name + ) my_shape = top.shape if len(self.input_parameter_order) != 0: parameter_string = "" diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 681cdde26e..ee8a72daa7 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1174,15 +1174,16 @@ def generate_julia_diffeq( get_consistent_ics_solver.set_up(self) get_consistent_ics_solver._set_initial_conditions(self, {}, False) ics = pybamm.Vector(self.y0.full()) + ics = pybamm.Addition(ics, pybamm.Scalar(0)) + ics_converter = pybamm.JuliaConverter( input_parameter_order=input_parameter_order, cache_type=cache_type, inline=inline, - preallocate=preallocate, + preallocate=False, ) ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name + "_ics") - ics_str.replace("(du, u, p, t)", "(u, p)") if generate_jacobian: size_state = self.concatenated_initial_conditions.size From 27257f781c8c394349b96b6b7ae3a47b2e275ee9 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 21:09:33 -0400 Subject: [PATCH 115/163] fix function arg --- pybamm/models/base_model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index ee8a72daa7..057d80c87f 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1184,6 +1184,7 @@ def generate_julia_diffeq( ) ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name + "_ics") + ics_str = ics_str.replace("(dy, y, p, t)","(u0, p)") if generate_jacobian: size_state = self.concatenated_initial_conditions.size From bac1a3be29de8c442704e0cacd31ec5d46c61608 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 21:13:51 -0400 Subject: [PATCH 116/163] preallocate --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 057d80c87f..58b242c11b 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1180,7 +1180,7 @@ def generate_julia_diffeq( input_parameter_order=input_parameter_order, cache_type=cache_type, inline=inline, - preallocate=False, + preallocate=True, ) ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name + "_ics") From eaf1f6b6bda6c5f50134066cb7e9d27fbbe0baf2 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 21:16:31 -0400 Subject: [PATCH 117/163] typo --- pybamm/models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 58b242c11b..b140891e76 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1184,7 +1184,7 @@ def generate_julia_diffeq( ) ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name + "_ics") - ics_str = ics_str.replace("(dy, y, p, t)","(u0, p)") + ics_str = ics_str.replace("(dy, y, p, t)","(dy, p)") if generate_jacobian: size_state = self.concatenated_initial_conditions.size From 60e28e57c75989710b9fdfdb93620ac0d397ca10 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 12 Oct 2022 22:03:27 -0400 Subject: [PATCH 118/163] fix tests and revert ics --- .../operations/evaluate_julia.py | 2 +- pybamm/models/base_model.py | 4 ++-- tests/unit/test_models/test_base_model.py | 8 +++++-- .../test_base_model_generate_julia_diffeq.py | 22 +++++++++++++------ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 1f1ab8a522..b3e8b2d266 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -535,7 +535,7 @@ def write_function_easy(self, funcname, inline=True): self._cache_and_const_string = ( "begin\n{} = let \n".format(funcname) + self._cache_and_const_string ) - if len(self._intermediate)>1: + if len(self._intermediate) > 1: self._cache_and_const_string = remove_lines_with( self._cache_and_const_string, top_var_name ) diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index b140891e76..11ad462f69 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -1175,7 +1175,7 @@ def generate_julia_diffeq( get_consistent_ics_solver._set_initial_conditions(self, {}, False) ics = pybamm.Vector(self.y0.full()) ics = pybamm.Addition(ics, pybamm.Scalar(0)) - + ics_converter = pybamm.JuliaConverter( input_parameter_order=input_parameter_order, cache_type=cache_type, @@ -1184,7 +1184,7 @@ def generate_julia_diffeq( ) ics_converter.convert_tree_to_intermediate(ics) ics_str = ics_converter.build_julia_code(funcname=name + "_ics") - ics_str = ics_str.replace("(dy, y, p, t)","(dy, p)") + ics_str = ics_str.replace("(dy, y, p, t)", "(dy, p)") if generate_jacobian: size_state = self.concatenated_initial_conditions.size diff --git a/tests/unit/test_models/test_base_model.py b/tests/unit/test_models/test_base_model.py index 058009bf6b..ecb15601e2 100644 --- a/tests/unit/test_models/test_base_model.py +++ b/tests/unit/test_models/test_base_model.py @@ -611,7 +611,9 @@ def test_generate_julia_diffeq(self): rhs_str, ics_str = model.generate_julia_diffeq() self.assertIsInstance(rhs_str, str) self.assertIn("ode_test_model", rhs_str) - self.assertIsInstance(ics_str, pybamm.Vector) + self.assertIsInstance(ics_str, str) + self.assertIn("ode_test_model_ics", ics_str) + self.assertIn("(dy, p)", ics_str) # ODE model with input parameters model = pybamm.BaseModel(name="ode test model 2") @@ -628,7 +630,9 @@ def test_generate_julia_diffeq(self): self.assertIn("ode_test_model_2", rhs_str) self.assertIn("p,q,= p", rhs_str) - self.assertIsInstance(ics_str, pybamm.Vector) + self.assertIsInstance(ics_str, str) + self.assertIn("ode_test_model_2_ics", ics_str) + self.assertIn("p,q,= p", ics_str) def test_set_initial_conditions(self): # Set up model diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index bb173cea04..fdec9c23fa 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -4,7 +4,6 @@ import platform import unittest import pybamm -import numpy as np have_julia = True # pybamm.have_julia() if have_julia and platform.system() != "Windows": @@ -29,7 +28,9 @@ def test_generate_ode(self): self.assertIsInstance(rhs_str, str) self.assertIn("ode_test_model", rhs_str) self.assertIn("(dy, y, p, t)", rhs_str) - self.assertIsInstance(ics_str, pybamm.Vector) + self.assertIsInstance(ics_str, str) + self.assertIn("ode_test_model_ics", ics_str) + self.assertIn("(dy, p)", ics_str) # ODE model with input parameters model = pybamm.BaseModel(name="ode test model 2") @@ -46,7 +47,9 @@ def test_generate_ode(self): self.assertIn("ode_test_model_2", rhs_str) self.assertIn("p,q,= p", rhs_str) - self.assertIsInstance(ics_str, pybamm.Vector) + self.assertIsInstance(ics_str, str) + self.assertIn("ode_test_model_2_ics", ics_str) + self.assertIn("p,q,= p", ics_str) def test_generate_dae(self): # ODE model with no input parameters @@ -62,21 +65,24 @@ def test_generate_dae(self): self.assertIsInstance(eqn_str, str) self.assertIn("dae_test_model", eqn_str) self.assertIn("(dy, y, p, t)", eqn_str) - self.assertIsInstance(ics_str, pybamm.Vector) + self.assertIsInstance(ics_str, str) + self.assertIn("dae_test_model_ics", ics_str) + self.assertIn("(dy, p)", ics_str) + self.assertIn("[[1.]\n [2.]]", ics_str) # Generate eqn and ics for the Julia model (implicit) eqn_str, ics_str = model.generate_julia_diffeq(dae_type="implicit") self.assertIsInstance(eqn_str, str) self.assertIn("dae_test_model", eqn_str) self.assertIn("(out, dy, y, p, t)", eqn_str) - self.assertIsInstance(ics_str, pybamm.Vector) + self.assertIsInstance(ics_str, str) + self.assertIn("dae_test_model_ics", ics_str) # Calculate initial conditions in python eqn_str, ics_str = model.generate_julia_diffeq( get_consistent_ics_solver=pybamm.CasadiSolver() ) # Check that the initial conditions are consistent - self.assertEqual(pybamm.Vector(np.array([1., 1.])), ics_str) def test_generate_pde(self): # ODE model with no input parameters @@ -105,7 +111,9 @@ def test_generate_pde(self): self.assertIsInstance(rhs_str, str) self.assertIn("pde_test_model", rhs_str) self.assertIn("(dy, y, p, t)", rhs_str) - self.assertIsInstance(ics_str, pybamm.Vector) + self.assertIsInstance(ics_str, str) + self.assertIn("pde_test_model_ics", ics_str) + self.assertIn("(dy, p)", ics_str) if __name__ == "__main__": From 92bd3f1da7244b28413c6aaf5cc539eeed76579a Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 13 Oct 2022 10:52:21 -0400 Subject: [PATCH 119/163] merge and test; fix style issues --- .../operations/evaluate_julia.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 23a16f27eb..88bf8c5377 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -20,23 +20,6 @@ def get_lower_keys(key, all_keys): return my_lower_keys - -def is_constant_and_can_evaluate(symbol): - """ - Returns True if symbol is constant and evaluation does not raise any errors. - Returns False otherwise. - An example of a constant symbol that cannot be "evaluated" is PrimaryBroadcast(0). - """ - if symbol.is_constant(): - try: - symbol.evaluate() - return True - except NotImplementedError: - return False - else: - return False - - def remove_lines_with(input_string, pattern): string_list = input_string.split("\n") my_string = "" From b61590765eb92d954e0c97d9bde355059ee5b68d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 14:56:59 +0000 Subject: [PATCH 120/163] style: pre-commit fixes --- README.md | 2 ++ .../test_operations/test_evaluate_julia.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eaea0cf618..8027d4fa04 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ [![black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) + [![All Contributors](https://img.shields.io/badge/all_contributors-46-orange.svg)](#-contributors) + diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index b030b6a17d..83968c0ef1 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -46,11 +46,11 @@ def evaluate_and_test_equal( try: Main.seval(evaluator_str) except JuliaError as e: - #text_file = open( + # text_file = open( # "julia_evaluator_{}.jl".format(kwargs["funcname"]), "w" - #) - #text_file.write(evaluator_str) - #text_file.close() + # ) + # text_file.write(evaluator_str) + # text_file.close() raise e for t_test, y_test in zip(t_tests, y_tests): From d1f48a84b83326cf263b4a165d39b1abfc5377e1 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 13 Oct 2022 11:48:25 -0400 Subject: [PATCH 121/163] fix setup in attempt to pass test --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d69aa5bc6b..7536d5a248 100644 --- a/setup.py +++ b/setup.py @@ -199,7 +199,6 @@ def compile_KLU(): "imageio>=2.9.0", # Julia pip packaged can be installed even if # julia programming language is not installed - "julia>=0.5.6", "jupyter", # For example notebooks "pybtex>=0.24.0", "sympy>=1.8", @@ -208,6 +207,9 @@ def compile_KLU(): # outside of plot() methods. # Should not be imported "matplotlib>=2.0", + "juliacall>=0.1.0", + "juliapkg>=0.1.9", + "graphlib-backport; python_version<'3.9'", ], extras_require={ "docs": ["sphinx>=1.5", "guzzle-sphinx-theme"], # For doc generation From 48bc8ad116590eaf88f4533de829cab31049b042 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 13 Oct 2022 16:19:29 -0400 Subject: [PATCH 122/163] fix codacy stuff --- .../operations/evaluate_julia.py | 82 +++++++------------ 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 88bf8c5377..a504df19eb 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -3,21 +3,10 @@ # import pybamm import numpy as np -import numpy import scipy from collections import OrderedDict from math import floor import graphlib -from pybamm.util import is_constant_and_can_evaluate - - -def get_lower_keys(key, all_keys): - key_length = len(key) - all_lower_keys = list(filter(lambda this_key: len(this_key) > key_length, all_keys)) - my_lower_keys = list( - filter(lambda this_key: this_key[0:key_length] == key, all_lower_keys) - ) - return my_lower_keys def remove_lines_with(input_string, pattern): @@ -37,11 +26,15 @@ def __init__( jacobian_type="analytical", preallocate=True, dae_type="semi-explicit", - input_parameter_order=[], + input_parameter_order=None, inline=True, parallel="legacy-serial", ): - assert not ismtk + if ismtk: + raise NotImplementedError("mtk is not supported") + + if input_parameter_order is None: + input_parameter_order = [] if parallel != "legacy-serial" and inline: raise NotImplementedError( @@ -89,8 +82,8 @@ def __init__( def cache_exists(self, my_id, inputs): existance = self._cache_dict.get(my_id) is not None if existance: - for input in inputs: - self._dag[my_id].add(input) + for this_input in inputs: + self._dag[my_id].add(this_input) return self._cache_dict.get(my_id) is not None # know where to go to find a variable. @@ -102,10 +95,6 @@ def cache_exists(self, my_id, inputs): def break_down_binary(self, symbol): # Check for constant # assert not is_constant_and_can_evaluate(symbol) - - # We know that this should only have 2 children - assert len(symbol.children) == 2 - # take care of the kids first (this is recursive # but multiple-dispatch recursive which is cool) id_left = self._convert_tree_to_intermediate(symbol.children[0]) @@ -123,7 +112,6 @@ def break_down_concatenation(self, symbol): num_rows = 0 for child_id in child_ids: child_shape = self._intermediate[child_id].shape - assert num_cols == child_shape[1] num_rows += child_shape[0] shape = (num_rows, num_cols) return child_ids, shape @@ -222,24 +210,23 @@ def _convert_tree_to_intermediate(self, symbol): id_left, id_right, my_id, my_shape, "<" ) elif isinstance(symbol, pybamm.Index): - assert len(symbol.children) == 1 id_lower = self._convert_tree_to_intermediate(symbol.children[0]) child_shape = self._intermediate[id_lower].shape child_ncols = child_shape[1] my_id = symbol.id index = symbol.index - if type(index) is slice: + if isinstance(index, slice): if index.step is None: shape = ((index.stop) - (index.start), child_ncols) - elif type(index.step) == int: + elif isinstance(index.step, int): shape = ( floor((index.stop - index.start) / index.step), child_ncols, ) else: raise NotImplementedError("index must be slice or int") - elif type(index) is int: + elif isinstance(index, int): shape = (1, child_ncols) else: raise NotImplementedError("index must be slice or int") @@ -247,33 +234,29 @@ def _convert_tree_to_intermediate(self, symbol): self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index, shape) elif isinstance(symbol, pybamm.Min) or isinstance(symbol, pybamm.Max): my_jl_name = symbol.julia_name - assert len(symbol.children) == 1 my_shape = (1, 1) - input = self._convert_tree_to_intermediate(symbol.children[0]) + this_input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id self._intermediate[my_id] = JuliaMinimumMaximum( - my_jl_name, input, my_id, my_shape + my_jl_name, this_input, my_id, my_shape ) elif isinstance(symbol, pybamm.Function): my_jl_name = symbol.julia_name - assert len(symbol.children) == 1 my_shape = symbol.children[0].shape - input = self._convert_tree_to_intermediate(symbol.children[0]) + this_input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id self._intermediate[my_id] = JuliaBroadcastableFunction( - my_jl_name, input, my_id, my_shape + my_jl_name, this_input, my_id, my_shape ) elif isinstance(symbol, pybamm.Negate): my_jl_name = "-" - assert len(symbol.children) == 1 my_shape = symbol.children[0].shape - input = self._convert_tree_to_intermediate(symbol.children[0]) + this_input = self._convert_tree_to_intermediate(symbol.children[0]) my_id = symbol.id self._intermediate[my_id] = JuliaNegation( - my_jl_name, input, my_id, my_shape + my_jl_name, this_input, my_id, my_shape ) elif isinstance(symbol, pybamm.Matrix): - assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() if value.shape == (1, 1): @@ -281,7 +264,6 @@ def _convert_tree_to_intermediate(self, symbol): else: self._intermediate[my_id] = JuliaConstant(my_id, value) elif isinstance(symbol, pybamm.Vector): - assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() if value.shape == (1, 1): @@ -291,7 +273,6 @@ def _convert_tree_to_intermediate(self, symbol): else: self._intermediate[my_id] = JuliaConstant(my_id, value) elif isinstance(symbol, pybamm.Scalar): - assert is_constant_and_can_evaluate(symbol) my_id = symbol.id value = symbol.evaluate() self._intermediate[my_id] = JuliaScalar(my_id, value) @@ -347,9 +328,6 @@ def find_broadcastable_shape(self, id_left, id_right): elif (left_shape[1] == 1) & (right_shape[0] == left_shape[0]): return right_shape else: - print("Right type is {}".format(type(self._intermediate[id_right]))) - print("Right Shape is {}".format(right_shape)) - print("Left Shape is {}".format(left_shape)) raise NotImplementedError( "multiplication for the shapes youve requested doesnt work." ) @@ -360,7 +338,8 @@ def find_broadcastable_shape(self, id_left, id_right): def same_shape(self, id_left, id_right): left_shape = self._intermediate[id_left].shape right_shape = self._intermediate[id_right].shape - assert left_shape == right_shape + if left_shape != right_shape: + raise AssertionError("the shapes are not the same") return left_shape # Functions @@ -452,7 +431,7 @@ def create_const(self, symbol): return 0 def write_const(self, value): - if isinstance(value, numpy.ndarray): + if isinstance(value, np.ndarray): val_string = value elif isinstance(value, scipy.sparse._csr.csr_matrix): row, col, data = scipy.sparse.find(value) @@ -567,7 +546,6 @@ def write_function_easy(self, funcname, inline=True): # this function will be the top level. def convert_tree_to_intermediate(self, symbol, len_rhs=None): if self._dae_type == "implicit": - assert len_rhs is not None symbol_minus_dy = [] end = 0 for child in symbol.orphans: @@ -763,9 +741,9 @@ class JuliaFunction(object): class JuliaBroadcastableFunction(JuliaFunction): - def __init__(self, name, input, output, shape): + def __init__(self, name, this_input, output, shape): self.name = name - self.input = input + self.input = this_input self.output = output self.shape = shape @@ -844,8 +822,8 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): # Index is a little weird, so it just sits on its own. class JuliaIndex(object): - def __init__(self, input, output, index, shape): - self.input = input + def __init__(self, this_input, output, index, shape): + self.input = this_input self.output = output self.index = index self.shape = shape @@ -870,14 +848,14 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): input_var_name = converter._intermediate[ self.input ]._convert_intermediate_to_code(converter, inline=False) - if type(index) is int: + if isinstance(index, int): return "{}[{}]".format(input_var_name, index + 1) - elif type(index) is slice: + elif isinstance(index, slice): if index.step is None: return "(@view {}[{}:{}{})".format( input_var_name, index.start + 1, index.stop, right_parenthesis ) - elif type(index.step) is int: + elif isinstance(index.step, int): return "(@view {}[{}:{}:{}{})".format( input_var_name, index.start + 1, @@ -894,11 +872,11 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): input_var_name = converter._intermediate[ self.input ]._convert_intermediate_to_code(converter, inline=False) - if type(index) is int: + if isinstance(index, int): code = "@. {} = {}[{}{}".format( result_var_name, input_var_name, index + 1, right_parenthesis ) - elif type(index) is slice: + elif isinstance(index, slice): if index.step is None: code = "@. {} = (@view {}[{}:{}{})\n".format( result_var_name, @@ -907,7 +885,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): index.stop, right_parenthesis, ) - elif type(index.step) is int: + elif isinstance(index.step, int): code = "@. {} = (@view {}[{}:{}:{}{})\n".format( result_var_name, input_var_name, From 21b5fb2849ddbeb5b76c4c02c60918b62af8be17 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 13 Oct 2022 16:31:01 -0400 Subject: [PATCH 123/163] add test for jacobian --- .../test_base_model_generate_julia_diffeq.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index fdec9c23fa..1945bfdcc9 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -70,6 +70,15 @@ def test_generate_dae(self): self.assertIn("(dy, p)", ics_str) self.assertIn("[[1.]\n [2.]]", ics_str) + # Calculate initial conditions in python + eqn_str, ics_str, jac_str = model.generate_julia_diffeq( + get_consistent_ics_solver=pybamm.CasadiSolver(), + generate_jacobian = True + ) + # Check the jacobian + self.assertIn("dae_test_model_jac", jac_str) + self.assertIn("(J, y, p, t)", jac_str) + # Generate eqn and ics for the Julia model (implicit) eqn_str, ics_str = model.generate_julia_diffeq(dae_type="implicit") self.assertIsInstance(eqn_str, str) @@ -78,11 +87,6 @@ def test_generate_dae(self): self.assertIsInstance(ics_str, str) self.assertIn("dae_test_model_ics", ics_str) - # Calculate initial conditions in python - eqn_str, ics_str = model.generate_julia_diffeq( - get_consistent_ics_solver=pybamm.CasadiSolver() - ) - # Check that the initial conditions are consistent def test_generate_pde(self): # ODE model with no input parameters From 7720855cfb6950b65c64715d844c718774e75b1b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 20:33:12 +0000 Subject: [PATCH 124/163] style: pre-commit fixes --- .../test_models/test_base_model_generate_julia_diffeq.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index 1945bfdcc9..b071fcb961 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -70,10 +70,9 @@ def test_generate_dae(self): self.assertIn("(dy, p)", ics_str) self.assertIn("[[1.]\n [2.]]", ics_str) - # Calculate initial conditions in python + # Calculate initial conditions in python eqn_str, ics_str, jac_str = model.generate_julia_diffeq( - get_consistent_ics_solver=pybamm.CasadiSolver(), - generate_jacobian = True + get_consistent_ics_solver=pybamm.CasadiSolver(), generate_jacobian=True ) # Check the jacobian self.assertIn("dae_test_model_jac", jac_str) @@ -87,7 +86,6 @@ def test_generate_dae(self): self.assertIsInstance(ics_str, str) self.assertIn("dae_test_model_ics", ics_str) - def test_generate_pde(self): # ODE model with no input parameters model = pybamm.BaseModel(name="pde test model") From 80c8b118e974b9c1011a9ab73474321f97b1f7f0 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 13 Oct 2022 18:02:55 -0400 Subject: [PATCH 125/163] add a bunch of tests --- .../operations/evaluate_julia.py | 4 --- .../test_operations/test_evaluate_julia.py | 33 +++++++++++++++++++ .../test_base_model_generate_julia_diffeq.py | 2 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index a504df19eb..ef8863d034 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -224,12 +224,8 @@ def _convert_tree_to_intermediate(self, symbol): floor((index.stop - index.start) / index.step), child_ncols, ) - else: - raise NotImplementedError("index must be slice or int") elif isinstance(index, int): shape = (1, child_ncols) - else: - raise NotImplementedError("index must be slice or int") self._intermediate[my_id] = JuliaIndex(id_lower, my_id, index, shape) elif isinstance(symbol, pybamm.Min) or isinstance(symbol, pybamm.Max): diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 83968c0ef1..30d404451d 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -8,6 +8,7 @@ import numpy as np import scipy.sparse from platform import system +from collections import OrderedDict have_julia = pybamm.have_julia() if have_julia and system() != "Windows": @@ -87,6 +88,18 @@ def test_exceptions(self): myconverter = pybamm.JuliaConverter() myconverter.convert_tree_to_intermediate(a) myconverter.build_julia_code() + with self.assertRaisesRegex(NotImplementedError, "Inline not supported"): + myconverter = pybamm.JuliaConverter(parallel=None, inline=True) + + def test_converter_julia(self): + A = pybamm.Matrix(np.random.rand(2,2)) + b = pybamm.StateVector(slice(0,2)) + expr = A @ b + + converter = pybamm.JuliaConverter() + converter.convert_tree_to_intermediate(expr) + converter.clear() + self.assertEqual(converter._intermediate, OrderedDict()) def test_evaluator_julia(self): a = pybamm.StateVector(slice(0, 1)) @@ -128,10 +141,20 @@ def test_evaluator_julia(self): expr = A @ pybamm.StateVector(slice(0, 2)) self.evaluate_and_test_equal(expr, y_tests, funcname="g3") + # test something with a 1x1 matrix multiplication + Q = pybamm.Matrix(np.random.rand(1,1)) + expr = Q * pybamm.StateVector(slice(0, 1)) + self.evaluate_and_test_equal(expr, y_tests, funcname="a1") + # test something with a heaviside a = pybamm.Vector([1, 2]) expr = a <= pybamm.StateVector(slice(0, 2)) self.evaluate_and_test_equal(expr, y_tests, funcname="g4") + + #test something with a notequalheaviside + a = pybamm.Vector([1, 2]) + expr = a < pybamm.StateVector(slice(0, 2)) + self.evaluate_and_test_equal(expr, y_tests, funcname="a4") # test something with a minimum or maximum a = pybamm.Vector([1, 2]) @@ -145,6 +168,16 @@ def test_evaluator_julia(self): expr = pybamm.Index(A @ pybamm.StateVector(slice(0, 2)), 0) self.evaluate_and_test_equal(expr, y_tests, funcname="g6") + # test something with a slice index + expr = pybamm.Index(A @ pybamm.StateVector(slice(0, 2)), slice(0,1)) + self.evaluate_and_test_equal(expr, y_tests, funcname="a2") + + q_test = np.array([1,2,3,4,5,6]) + Q = pybamm.Matrix(np.random.rand(6,6)) + q = pybamm.StateVector(slice(0, 6)) + expr = pybamm.Index(Q @ q, slice(0,5,2)) + self.evaluate_and_test_equal(expr, q_test, funcname="a6", decimal=7) + # test something with a sparse matrix multiplication A = pybamm.Matrix([[1, 2], [3, 4]]) B = pybamm.Matrix(scipy.sparse.csr_matrix(np.array([[1, 0], [0, 4]]))) diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index 1945bfdcc9..a7c399b9b5 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -76,7 +76,7 @@ def test_generate_dae(self): generate_jacobian = True ) # Check the jacobian - self.assertIn("dae_test_model_jac", jac_str) + self.assertIn("jac_dae_test_model", jac_str) self.assertIn("(J, y, p, t)", jac_str) # Generate eqn and ics for the Julia model (implicit) From fbddb6e10d4c5e5267a4bd1009eecd5dbdc10df4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 22:05:34 +0000 Subject: [PATCH 126/163] style: pre-commit fixes --- .../test_operations/test_evaluate_julia.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 30d404451d..6452322184 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -90,10 +90,10 @@ def test_exceptions(self): myconverter.build_julia_code() with self.assertRaisesRegex(NotImplementedError, "Inline not supported"): myconverter = pybamm.JuliaConverter(parallel=None, inline=True) - + def test_converter_julia(self): - A = pybamm.Matrix(np.random.rand(2,2)) - b = pybamm.StateVector(slice(0,2)) + A = pybamm.Matrix(np.random.rand(2, 2)) + b = pybamm.StateVector(slice(0, 2)) expr = A @ b converter = pybamm.JuliaConverter() @@ -142,7 +142,7 @@ def test_evaluator_julia(self): self.evaluate_and_test_equal(expr, y_tests, funcname="g3") # test something with a 1x1 matrix multiplication - Q = pybamm.Matrix(np.random.rand(1,1)) + Q = pybamm.Matrix(np.random.rand(1, 1)) expr = Q * pybamm.StateVector(slice(0, 1)) self.evaluate_and_test_equal(expr, y_tests, funcname="a1") @@ -150,8 +150,8 @@ def test_evaluator_julia(self): a = pybamm.Vector([1, 2]) expr = a <= pybamm.StateVector(slice(0, 2)) self.evaluate_and_test_equal(expr, y_tests, funcname="g4") - - #test something with a notequalheaviside + + # test something with a notequalheaviside a = pybamm.Vector([1, 2]) expr = a < pybamm.StateVector(slice(0, 2)) self.evaluate_and_test_equal(expr, y_tests, funcname="a4") @@ -169,13 +169,13 @@ def test_evaluator_julia(self): self.evaluate_and_test_equal(expr, y_tests, funcname="g6") # test something with a slice index - expr = pybamm.Index(A @ pybamm.StateVector(slice(0, 2)), slice(0,1)) + expr = pybamm.Index(A @ pybamm.StateVector(slice(0, 2)), slice(0, 1)) self.evaluate_and_test_equal(expr, y_tests, funcname="a2") - q_test = np.array([1,2,3,4,5,6]) - Q = pybamm.Matrix(np.random.rand(6,6)) + q_test = np.array([1, 2, 3, 4, 5, 6]) + Q = pybamm.Matrix(np.random.rand(6, 6)) q = pybamm.StateVector(slice(0, 6)) - expr = pybamm.Index(Q @ q, slice(0,5,2)) + expr = pybamm.Index(Q @ q, slice(0, 5, 2)) self.evaluate_and_test_equal(expr, q_test, funcname="a6", decimal=7) # test something with a sparse matrix multiplication From bcf959c19fb7e390426f31df4d63ee9426388ab1 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Fri, 14 Oct 2022 11:04:19 -0400 Subject: [PATCH 127/163] coverage --- .../operations/evaluate_julia.py | 10 ----- .../test_operations/test_evaluate_julia.py | 22 +++++----- .../test_base_model_generate_julia_diffeq.py | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index ef8863d034..549de70587 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -328,16 +328,6 @@ def find_broadcastable_shape(self, id_left, id_right): "multiplication for the shapes youve requested doesnt work." ) - # to find the shape, there are a number of elements that should just - # have the shame shape as their children. This function removes - # boilerplate by implementing those cases - def same_shape(self, id_left, id_right): - left_shape = self._intermediate[id_left].shape - right_shape = self._intermediate[id_right].shape - if left_shape != right_shape: - raise AssertionError("the shapes are not the same") - return left_shape - # Functions # Broadcastable functions have 1 input and 1 output, and the # input and output have the same shape. The hard part is that diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 30d404451d..45f24b0ee5 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -90,10 +90,12 @@ def test_exceptions(self): myconverter.build_julia_code() with self.assertRaisesRegex(NotImplementedError, "Inline not supported"): myconverter = pybamm.JuliaConverter(parallel=None, inline=True) - + with self.assertRaisesRegex(NotImplementedError, "mtk is not supported"): + myconverter = pybamm.JuliaConverter(ismtk=True) + def test_converter_julia(self): - A = pybamm.Matrix(np.random.rand(2,2)) - b = pybamm.StateVector(slice(0,2)) + A = pybamm.Matrix(np.random.rand(2, 2)) + b = pybamm.StateVector(slice(0, 2)) expr = A @ b converter = pybamm.JuliaConverter() @@ -142,7 +144,7 @@ def test_evaluator_julia(self): self.evaluate_and_test_equal(expr, y_tests, funcname="g3") # test something with a 1x1 matrix multiplication - Q = pybamm.Matrix(np.random.rand(1,1)) + Q = pybamm.Matrix(np.random.rand(1, 1)) expr = Q * pybamm.StateVector(slice(0, 1)) self.evaluate_and_test_equal(expr, y_tests, funcname="a1") @@ -150,8 +152,8 @@ def test_evaluator_julia(self): a = pybamm.Vector([1, 2]) expr = a <= pybamm.StateVector(slice(0, 2)) self.evaluate_and_test_equal(expr, y_tests, funcname="g4") - - #test something with a notequalheaviside + + # test something with a notequalheaviside a = pybamm.Vector([1, 2]) expr = a < pybamm.StateVector(slice(0, 2)) self.evaluate_and_test_equal(expr, y_tests, funcname="a4") @@ -169,13 +171,13 @@ def test_evaluator_julia(self): self.evaluate_and_test_equal(expr, y_tests, funcname="g6") # test something with a slice index - expr = pybamm.Index(A @ pybamm.StateVector(slice(0, 2)), slice(0,1)) + expr = pybamm.Index(A @ pybamm.StateVector(slice(0, 2)), slice(0, 1)) self.evaluate_and_test_equal(expr, y_tests, funcname="a2") - q_test = np.array([1,2,3,4,5,6]) - Q = pybamm.Matrix(np.random.rand(6,6)) + q_test = np.array([1, 2, 3, 4, 5, 6]) + Q = pybamm.Matrix(np.random.rand(6, 6)) q = pybamm.StateVector(slice(0, 6)) - expr = pybamm.Index(Q @ q, slice(0,5,2)) + expr = pybamm.Index(Q @ q, slice(0, 5, 2)) self.evaluate_and_test_equal(expr, q_test, funcname="a6", decimal=7) # test something with a sparse matrix multiplication diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index 4ab0ad2f4b..e34868ebae 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -117,6 +117,46 @@ def test_generate_pde(self): self.assertIn("pde_test_model_ics", ics_str) self.assertIn("(dy, p)", ics_str) + def test_generate_model(self): + model = pybamm.lithium_ion.DFN(name="DFN") + sim = pybamm.Simulation(model) + sim.build() + model_str, ics_str, jac_str = sim.built_model.generate_julia_diffeq( + generate_jacobian=True + ) + self.assertIsInstance(model_str, str) + self.assertIn("DFN", model_str) + self.assertIn("(dy, y, p, t)", model_str) + self.assertIsInstance(ics_str, str) + self.assertIn("DFN_ics", ics_str) + self.assertIn("(dy, p)", ics_str) + self.assertIn("jac_DFN", jac_str) + self.assertIn("(J, y, p, t)", jac_str) + + model = pybamm.lithium_ion.DFN(name="DFN") + sim = pybamm.Simulation(model) + sim.build() + model_str, ics_str = sim.built_model.generate_julia_diffeq( + cache_type="dual", dae_type="implicit" + ) + self.assertIn("PreallocationTools", model_str) + + model = pybamm.lithium_ion.DFN(name="DFN") + sim = pybamm.Simulation(model) + sim.build() + model_str, ics_str, jac_str = sim.built_model.generate_julia_diffeq( + cache_type="gpu", generate_jacobian=True + ) + self.assertIn("cu", model_str) + + model = pybamm.lithium_ion.DFN(name="DFN") + sim = pybamm.Simulation(model) + sim.build() + model_str, ics_str, jac_str = sim.built_model.generate_julia_diffeq( + cache_type="symbolic", generate_jacobian=True + ) + self.assertIn("symcache", model_str) + if __name__ == "__main__": print("Add -v for more debug output") From a04559e39a33d2e64ff361ca99c2c1ec589545e3 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 18 Oct 2022 17:59:36 -0400 Subject: [PATCH 128/163] simple repeated cells --- pybamm/__init__.py | 5 +- .../expression_tree/operations/build_pack.py | 63 +++++++++++++++++++ pybamm/expression_tree/state_vector.py | 33 ++++++++++ test_pack.py | 27 ++++++++ .../test_operations/test_evaluate_julia.py | 9 +++ 5 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 pybamm/expression_tree/operations/build_pack.py create mode 100644 test_pack.py diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 188ded5945..bdb1d0086b 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -99,9 +99,8 @@ from .expression_tree.operations.convert_to_casadi import CasadiConverter from .expression_tree.operations.unpack_symbols import SymbolUnpacker from .expression_tree.operations.replace_symbols import SymbolReplacer -from .expression_tree.operations.evaluate_julia import ( - JuliaConverter, -) +from .expression_tree.operations.evaluate_julia import JuliaConverter +from .expression_tree.operations.build_pack import Pack # # Model classes diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py new file mode 100644 index 0000000000..4e5e569202 --- /dev/null +++ b/pybamm/expression_tree/operations/build_pack.py @@ -0,0 +1,63 @@ +# +# convert an expression tree into a pack model +# +import pybamm +from copy import deepcopy + + +class Pack(object): + def __init__(self, built_model, num_cells): + # this is going to be a work in progress for a while: + # for now, will just do it at the julia level + self.cell_size = built_model.size + self.cell_model = built_model + self._offset = self.cell_size + self.built_model = built_model + self._sv_done = [] + self._cells = (built_model,) + self.repeat_cells(num_cells) + + def repeat_cells(self, num_cells): + for n in range(num_cells - 1): + self.add_new_cell() + self._offset += self.cell_size + self._sv_done = [] + print("adding cell {} of {}".format(n, num_cells)) + self.built_model = pybamm.NumpyConcatenation(*self._cells) + self.built_model.set_id() + print("done building pack") + + def add_new_cell(self): + new_model = deepcopy(self.cell_model) + # at some point need to figure out parameters + self.add_offset_to_state_vectors(new_model) + self._cells += (new_model,) + + def add_offset_to_state_vectors(self, symbol): + # this function adds an offset to the state vectors + new_y_slices = () + if isinstance(symbol, pybamm.StateVector): + # need to make sure its in place + if symbol.id not in self._sv_done: + for this_slice in symbol.y_slices: + start = this_slice.start + self._offset + stop = this_slice.stop + self._offset + step = this_slice.step + new_slice = slice(start, stop, step) + new_y_slices += (new_slice,) + symbol.replace_y_slices(*new_y_slices) + symbol.set_id() + self._sv_done += [symbol.id] + + elif isinstance(symbol, pybamm.StateVectorDot): + raise NotImplementedError("Idk what this means") + else: + for child in symbol.children: + self.add_offset_to_state_vectors(child) + child.set_id() + symbol.set_id() + + +class InternalPackParameter(object): + def __init__(self): + pass diff --git a/pybamm/expression_tree/state_vector.py b/pybamm/expression_tree/state_vector.py index 68c6a4fe5d..8ba02b199a 100644 --- a/pybamm/expression_tree/state_vector.py +++ b/pybamm/expression_tree/state_vector.py @@ -108,9 +108,15 @@ def set_evaluation_array(self, y_slices, evaluation_array): def set_id(self): """See :meth:`pybamm.Symbol.set_id()`""" + starts = tuple([s.start for s in self.y_slices]) + stops = tuple([s.stop for s in self.y_slices]) + steps = tuple([s.step for s in self.y_slices]) self._id = hash( (self.__class__, self.name, tuple(self.evaluation_array)) + tuple(self.domain) + + starts + + stops + + steps ) def _jac_diff_vector(self, variable): @@ -196,6 +202,33 @@ def _evaluate_for_shape(self): """ return np.nan * np.ones((self.size, 1)) + def replace_y_slices(self, *new_y_slices): + for y_slice in new_y_slices: + if not isinstance(y_slice, slice): + raise TypeError("all y_slices must be slice objects") + name_split = self.name.split("[") + base_name = name_split[0] + if new_y_slices[0].start is None: + name = base_name + "[:{:d}".format(y_slice.stop) + else: + name = base_name + "[{:d}:{:d}".format( + new_y_slices[0].start, new_y_slices[0].stop + ) + if len(new_y_slices) > 1: + name += ",{:d}:{:d}".format(new_y_slices[1].start, new_y_slices[1].stop) + if len(new_y_slices) > 2: + name += ",...,{:d}:{:d}]".format( + new_y_slices[-1].start, new_y_slices[-1].stop + ) + else: + name += "]" + else: + name += "]" + self._y_slices = new_y_slices + self._first_point = new_y_slices[0].start + self._last_point = new_y_slices[-1].stop + self.set_evaluation_array(new_y_slices, None) + class StateVector(StateVectorBase): """ diff --git a/test_pack.py b/test_pack.py new file mode 100644 index 0000000000..c5f1677480 --- /dev/null +++ b/test_pack.py @@ -0,0 +1,27 @@ +import pybamm +import numpy as np + +a = pybamm.StateVector(slice(0, 2)) +A = pybamm.Matrix(np.random.rand(2, 2)) +expr = pybamm.numpy_concatenation(A@a, a) + +model = pybamm.lithium_ion.DFN(name="DFN") +sim = pybamm.Simulation(model) +sim.build() + + +expr = pybamm.numpy_concatenation( + sim.built_model.concatenated_rhs, + sim.built_model.concatenated_algebraic +) + +pack = pybamm.Pack(expr, 50) + + +myconverter = pybamm.JuliaConverter(parallel=None,inline=False, cache_type="gpu") +myconverter.convert_tree_to_intermediate(pack.built_model) +jl_str = myconverter.build_julia_code() + +with open("pack.jl","w") as f: + f.write(jl_str) + f.close() \ No newline at end of file diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 45f24b0ee5..5415ecb0e2 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -103,6 +103,15 @@ def test_converter_julia(self): converter.clear() self.assertEqual(converter._intermediate, OrderedDict()) + def test_pack(self): + a = pybamm.StateVector(slice(0, 2)) + A = pybamm.Matrix(np.random.rand(2, 2)) + + expr = A @ a + y_test = np.random.rand(6) + pack = pybamm.Pack(expr, 2) + self.evaluate_and_test_equal(pack.built_model, y_test, decimal=1e-8) + def test_evaluator_julia(self): a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) From c718b21a1d27b32e7bd4d763ef1c2cbb16e22668 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 19 Oct 2022 14:47:44 -0400 Subject: [PATCH 129/163] add psuedoinputparameter --- file.jl | 377 ++++++++++++++++++++++ pybamm/__init__.py | 2 +- pybamm/expression_tree/input_parameter.py | 8 + test_pack.py | 23 +- 4 files changed, 389 insertions(+), 21 deletions(-) create mode 100644 file.jl diff --git a/file.jl b/file.jl new file mode 100644 index 0000000000..f580b933ef --- /dev/null +++ b/file.jl @@ -0,0 +1,377 @@ +begin +f = let +cache_1 = zeros(20) +const_1 = sparse([ 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, + 9,10, 9,10,11,10,11,12,11,12,13,12,13,14,13,14,15,14,15,16,15,16,17,16, + 17,18,17,18,19,18,19,20,19,20], [ 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, + 9, 9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16,16,16,17, + 17,17,18,18,18,19,19,19,20,20], Float64[-10576.1491769 , 1510.87845384, 10576.1491769 , -7554.39226921, + 2226.55772145, 6043.51381537, -7236.31259472, 2572.57682681, + 5009.75487327, -7146.04674115, 2774.07191525, 4573.46991433, + -7108.55928283, 2905.53548816, 4334.48736758, -7089.50659111, + 2997.96354621, 4183.97110295, -7078.52503966, 3066.45745366, + 4080.56149345, -7071.62637272, 3119.23293697, 4005.16891906, + -7067.01212281, 3161.13683885, 3947.77918585, -7063.77491151, + 3195.21123169, 3902.63807266, -7061.41682204, 3223.46108414, + 3866.20559035, -7059.64617602, 3247.26115453, 3836.18509187, + -7058.28292616, 3267.58539469, 3811.02177163, -7057.21105954, + 3285.14300899, 3789.62566485, -7056.35309584, 3300.46264189, + 3771.21008685, -7055.65569222, 3313.94637611, 3755.19305033, + -7055.08115228, 3325.90545389, 3741.13477616, -7054.60222572, + 3336.58455045, 3728.69677183, -7054.19881809, 3346.17866158, + 3717.61426763, -3346.17866158], 20, 20) +const_2 = [[ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [-26.29272568]] +cache_2 = zeros(20) +const_3 = sparse([ 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, + 9,10, 9,10,11,10,11,12,11,12,13,12,13,14,13,14,15,14,15,16,15,16,17,16, + 17,18,17,18,19,18,19,20,19,20], [ 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, + 9, 9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16,16,16,17, + 17,17,18,18,18,19,19,19,20,20], Float64[-27118.33122282, 3874.04731755, 27118.33122282,-19370.23658773, + 5709.1223627 , 15496.18927018,-18554.64767877, 6596.35083798, + 12845.52531607,-18323.19677217, 7113.0049109 , 11726.84593419, + -18227.07508419, 7450.09099528, 11114.07017329,-18178.22202848, + 7687.08601592, 10728.1310332 ,-18150.06420425, 7862.71141963, + 10462.97818833,-18132.37531466, 7998.03317171, 10269.66389503, + -18120.54390465, 8105.47907398, 10122.51073294,-18112.24336284, + 8192.84931203, 10006.76428886,-18106.19697958, 8265.28483114, + 9913.34766756,-18101.65686158, 8326.31065263, 9836.37203044, + -18098.16134913, 8378.42408895, 9771.85069649,-18095.41297318, + 8423.44361279, 9716.98888423,-18093.21306625, 8462.72472279, + 9669.76945346,-18091.42485184, 8497.29840029, 9628.70012904, + -18089.9516725 , 8527.96270228, 9592.65327221,-18088.7236557 , + 8555.34500116, 9560.76095342,-18087.68927715, 8579.9452861 , + 9532.34427598, -8579.9452861 ], 20, 20) +const_4 = [[ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [15.39019111]] +cache_3 = zeros(60) +const_5 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, + 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, + 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, + 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, + 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, + 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, + 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, + 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, + 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60,61], Float64[ -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45.,-180., 180.,-180., 180.,-180., 180.,-180., 180., + -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., + -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., + -180., 180.,-180., 180.,-180., 180.,-180., 180., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45.], 60, 61) +cache_4 = zeros(61) +cache_5 = zeros(61) +const_6 = sparse([ 1, 1,61,61], [ 1, 2,59,60], Float64[ 1.5,-0.5,-0.5, 1.5], 61, 60) +cache_6 = zeros(60) +const_7 = [[-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-1.91554083] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548] + [-0.31475548]] +cache_7 = zeros(60) +cache_8 = zeros(61) +const_8 = sparse([ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, + 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, + 50,51,52,53,54,55,56,57,58,59,60], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, + 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 61, 59) +cache_9 = zeros(59) +cache_10 = zeros(59) +const_9 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, + 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, + 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) +cache_11 = zeros(59) +const_10 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, + 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59], [ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, + 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, + 50,51,52,53,54,55,56,57,58,59,60], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., + 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) +cache_12 = zeros(59) +const_11 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, + 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, + 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, + 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, + 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, + 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, + 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, + 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, + 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, + 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, + 0.5,0.5,0.2,0.8,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, + 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, + 0.5,0.5,0.5,0.5,0.5,0.5,0.8,0.2,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, + 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, + 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5], 59, 60) +cache_13 = zeros(61) +const_12 = sparse([ 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13,13, + 14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25, + 26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37, + 38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49,49, + 50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, + 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, + 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, + 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, + 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[ -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -72., 72.,-180., 180.,-180., 180.,-180., 180.,-180., 180., + -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., + -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., + -180., 180.,-180., 180.,-180., 180., -72., 72., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., + -45., 45., -45., 45., -45., 45., -45., 45., -45., 45.], 61, 60) +cache_14 = zeros(60) +const_13 = [[ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.18068626] + [ 0.1355147 ] + [ 0.18068626] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [-0.18068626] + [-0.1355147 ] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626] + [-0.18068626]] +const_14 = [[ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 56.21233949] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [ 0. ] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949] + [-56.21233949]] +const_15 = [] + +function f_with_consts(dy, y, p, t) + +mul!(cache_1,const_1,(@view y[1:20])) +mul!(cache_2,const_3,(@view y[21:40])) +@. cache_7[1:20] = ((@view y[41:60]) * 3333.3333333333335) +@. cache_7[21:40] = ((@view y[61:80]) * 1000.0) +@. cache_7[41:60] = ((@view y[81:100]) * 3333.3333333333335) +@. cache_6 = const_7 * (exp((-0.00065 * max(cache_7,10.0)))) +mul!(cache_5,const_6,cache_6) +mul!(cache_10,const_9,cache_6) +mul!(cache_11,const_10,cache_6) +mul!(cache_12,const_11,cache_6) +@. cache_9 = (cache_10 * cache_11) / (cache_12 + 1e-16) +mul!(cache_8,const_8,cache_9) +@. cache_14[1:20] = ((@view y[41:60]) / 0.3) +@. cache_14[21:40] = (@view y[61:80]) +@. cache_14[41:60] = ((@view y[81:100]) / 0.3) +mul!(cache_13,const_12,cache_14) +@. cache_4 = (cache_5 + cache_8) * cache_13 +mul!(cache_3,const_5,cache_4) +@. dy[1:20] = (cache_1 + const_2) +@. dy[21:40] = (cache_2 + const_4) +@. dy[41:100] = (((cache_3 + const_13) / -0.008035880643729006) + const_14) + + return nothing +end +end +end \ No newline at end of file diff --git a/pybamm/__init__.py b/pybamm/__init__.py index bdb1d0086b..1b7c30b574 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -73,7 +73,7 @@ from .expression_tree.broadcasts import * from .expression_tree.functions import * from .expression_tree.interpolant import Interpolant -from .expression_tree.input_parameter import InputParameter +from .expression_tree.input_parameter import InputParameter, PsuedoInputParameter from .expression_tree.parameter import Parameter, FunctionParameter from .expression_tree.scalar import Scalar from .expression_tree.variable import * diff --git a/pybamm/expression_tree/input_parameter.py b/pybamm/expression_tree/input_parameter.py index 62c08bf0fd..2f703a0ef9 100644 --- a/pybamm/expression_tree/input_parameter.py +++ b/pybamm/expression_tree/input_parameter.py @@ -101,3 +101,11 @@ def _base_evaluate(self, t=None, y=None, y_dot=None, inputs=None): self._expected_size ) ) + +class PsuedoInputParameter(InputParameter): + def create_copy(self): + """See :meth:`pybamm.Symbol.new_copy()`.""" + new_input_parameter = PsuedoInputParameter( + self.name, self.domain, expected_size=self._expected_size + ) + return new_input_parameter diff --git a/test_pack.py b/test_pack.py index c5f1677480..e29b6907a3 100644 --- a/test_pack.py +++ b/test_pack.py @@ -1,27 +1,10 @@ import pybamm import numpy as np -a = pybamm.StateVector(slice(0, 2)) -A = pybamm.Matrix(np.random.rand(2, 2)) -expr = pybamm.numpy_concatenation(A@a, a) - model = pybamm.lithium_ion.DFN(name="DFN") -sim = pybamm.Simulation(model) +parameter_values = model.default_parameter_values +parameter_values.update({"Current function [A]":pybamm.PsuedoInputParameter("cell_current")}) +sim = pybamm.Simulation(model, parameter_values=parameter_values) sim.build() -expr = pybamm.numpy_concatenation( - sim.built_model.concatenated_rhs, - sim.built_model.concatenated_algebraic -) - -pack = pybamm.Pack(expr, 50) - - -myconverter = pybamm.JuliaConverter(parallel=None,inline=False, cache_type="gpu") -myconverter.convert_tree_to_intermediate(pack.built_model) -jl_str = myconverter.build_julia_code() - -with open("pack.jl","w") as f: - f.write(jl_str) - f.close() \ No newline at end of file From 996a0ed798185eacab52becd336c3c725dcbab27 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Fri, 21 Oct 2022 13:54:53 -0400 Subject: [PATCH 130/163] pack --- .../expression_tree/operations/build_pack.py | 240 +++++++++++++++++- test_pack.py | 10 - 2 files changed, 226 insertions(+), 24 deletions(-) delete mode 100644 test_pack.py diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index 4e5e569202..164053735c 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -3,24 +3,60 @@ # import pybamm from copy import deepcopy +import networkx as nx +import numpy as np +import pandas as pd +import liionpack as lp class Pack(object): - def __init__(self, built_model, num_cells): + def __init__(self, model, netlist, parameter_values=None): # this is going to be a work in progress for a while: # for now, will just do it at the julia level - self.cell_size = built_model.size - self.cell_model = built_model - self._offset = self.cell_size - self.built_model = built_model + + # Build the cell expression tree with necessary parameters. + # think about moving this to a separate function. + if parameter_values is not None: + raise AssertionError("parameter values not supported") + parameter_values = model.default_parameter_values + parameter_values.update( + {"Current function [A]": pybamm.PsuedoInputParameter("cell_current")} + ) + self.cell_parameter_values = parameter_values + sim = pybamm.Simulation(model, parameter_values=parameter_values) + sim.build() + self.cell_model = pybamm.numpy_concatenation( + sim.built_model.concatenated_rhs, sim.built_model.concatenated_algebraic + ) + self.cell_size = self.cell_model.shape[0] self._sv_done = [] - self._cells = (built_model,) - self.repeat_cells(num_cells) + + self.netlist = netlist + self.cell_currents = {} + + self.process_netlist() + + # get x and y coords for nodes from graph. + node_xs = [n for n in range(max(self.circuit_graph.nodes) + 1)] + node_ys = [n for n in range(max(self.circuit_graph.nodes) + 1)] + for row in netlist.itertuples(): + node_xs[row.node1] = row.node1_x + node_ys[row.node1] = row.node1_y + self.node_xs = node_xs + self.node_ys = node_ys + + def process_netlist(self): + curr = [{} for i in range(len(self.netlist))] + self.netlist.insert(0, "currents", curr) + self.netlist = self.netlist.rename( + columns={"node1": "source", "node2": "target"} + ) + self.circuit_graph = nx.from_pandas_edgelist(self.netlist, edge_attr=True) def repeat_cells(self, num_cells): for n in range(num_cells - 1): self.add_new_cell() - self._offset += self.cell_size + self.offset += self.cell_size self._sv_done = [] print("adding cell {} of {}".format(n, num_cells)) self.built_model = pybamm.NumpyConcatenation(*self._cells) @@ -28,10 +64,13 @@ def repeat_cells(self, num_cells): print("done building pack") def add_new_cell(self): + # TODO: deal with variables dict here too. + # This is the place to get clever. new_model = deepcopy(self.cell_model) # at some point need to figure out parameters self.add_offset_to_state_vectors(new_model) - self._cells += (new_model,) + new_model.set_id() + return new_model def add_offset_to_state_vectors(self, symbol): # this function adds an offset to the state vectors @@ -40,8 +79,8 @@ def add_offset_to_state_vectors(self, symbol): # need to make sure its in place if symbol.id not in self._sv_done: for this_slice in symbol.y_slices: - start = this_slice.start + self._offset - stop = this_slice.stop + self._offset + start = this_slice.start + self.offset + stop = this_slice.stop + self.offset step = this_slice.step new_slice = slice(start, stop, step) new_y_slices += (new_slice,) @@ -57,7 +96,180 @@ def add_offset_to_state_vectors(self, symbol): child.set_id() symbol.set_id() + def build_pack(self): + # this function builds expression trees to compute the current. + + # cycle basis is the list of loops over which we will do kirchoff mesh analysis + mcb = nx.minimum_cycle_basis(self.circuit_graph) + + # generate loop currents and current source voltages-- this is what we don't know. + num_loops = len(mcb) + num_curr_sources = sum([desc[0] == "I" for desc in self.netlist.desc]) + + loop_currents = [ + pybamm.StateVector(slice(n, n + 1), name="current_{}".format(n)) + for n in range(num_loops) + ] + curr_source_voltages = [ + pybamm.StateVector(slice(n, n + 1), name="current_source_{}".format(n)) + for n in range(num_loops, num_loops + num_curr_sources) + ] + + # now we know the offset, we should "build" the batteries here. will still need to replace the currents later. + self.offset = len(loop_currents) + len(curr_source_voltages) + self.batteries = {} + for desc in self.netlist.desc: + if desc[0] == "V": + new_cell = self.add_new_cell() + self.batteries.update({desc: new_cell}) + self.offset += self.cell_size + + if len(curr_source_voltages) != 1: + raise NotImplementedError("can't do this yet") + # copy the basis which we can use to place the loop currents + basis_to_place = deepcopy(mcb) + + self.place_currents(loop_currents, basis_to_place) + + def build_pack_equations(self, loop_currents, curr_source_voltages): + # start by looping through the loop currents. Sum Voltages + for i, loop_current in enumerate(loop_currents): + # loop through the edges + eq = [] + for edge in self.circuit_graph.edges: + if loop_current in edge["currents"]: + this_edge_equation = [] + edge_type = edge["desc"][0] + positive_direction = edge["currents"][loop_current] + this_edge_current = loop_current + for current in edge["currents"]: + if current == loop_current: + continue + if edge["currents"][current] == positive_direction: + this_edge_current = this_edge_current + current + else: + this_edge_current = this_edge_current - current + if edge_type == "R": + eq.append(this_edge_current * edge["value"]) + elif edge_type == "I": + curr_source_num = edge["desc"][1:] + if curr_source_num != "0": + raise NotImplementedError( + "multiple current sources is not yet supported" + ) + # need to check sign here. + eq.append(curr_source_voltages[0]) + elif edge_type == "V": + # voltage sources always point up. + # note that right now this is actually + # just a battery, not a voltage + voltage = self.batteries[edge["desc"]] + if ( + self.node_ys[positive_direction[0]] + > self.node_ys[positive_direction[1]] + ): + # voltage source is negative. + eq.append(-voltage) + else: + eq.append(voltage) + if len(eq) == 0: + raise NotImplementedError("uh oh") + elif len(eq) == 1: + expr = eq[0] + else: + expr = eq[0] + eq[1] + for e in range(2, len(eq)): + expr = expr + eq[e] + # add equation to the pack. + + # then loop through the current source voltages. Sum Currents. + + # This function places the currents on the edges in a predefined order. + # it begins at loop 0, and loops through each "loop" -- really a cycle + # of the mcb (minimum cycle basis) of the graph which defines the circuit. + # Once it finds a loop in which the current node is in, it places the + # loop current on each edge. Once the loop is finished, it removes the + # loop and then proceeds to the next node and does the same thing. It + # loops until all the loop currents have been placed. + def place_currents(self, loop_currents, mcb): + for node in sorted(self.circuit_graph.nodes): + if mcb == []: + # loops are all done! + break + this_loop = -1 + for i, loop in enumerate(mcb): + # in each loop use x and y to figure out direction. + # Within the same loop, go right first. + if node not in loop: + continue + if node in loop: + # print("starting loop {}".format(loop)) + # setting var to remove the loop later + this_loop = i + done_nodes = set() + # doesn't actually matter where we start. + # loop will always be a set. + if len(loop) != len(set(loop)): + raise NotImplementedError() + inner_node = node + # calculate the centroid of the loop + loop_xs = [self.node_xs[n] for n in loop] + loop_ys = [self.node_ys[n] for n in loop] + centroid_x = np.mean(loop_xs) + centroid_y = np.mean(loop_ys) + last_one = False + while True: + done_nodes.add(inner_node) + + my_neighbors = set( + self.circuit_graph.neighbors(inner_node) + ).intersection(set(loop)) + + # if there are no neighbors in the group that have not been done, ur done! + my_neighbors = my_neighbors - done_nodes + + if len(my_neighbors) == 0: + break + elif len(loop) == len(done_nodes) + 1 and not last_one: + last_one = True + done_nodes.remove(node) + + # calculate the angle to all the neighbors. + # then, to go clockwise, pick the one with + # the largest angle. + my_x = self.node_xs[inner_node] + my_y = self.node_ys[inner_node] + angles = { + n: np.arctan2( + self.node_xs[n] - centroid_x, + self.node_ys[n] - centroid_y, + ) + for n in my_neighbors + } + next_node = max(angles, key=angles.get) + # print("at node {}, now going to node {}".format(inner_node, next_node)) + # print(len(angles)) + + # now, define the vector from the current node to the next node. + next_coords = [ + self.node_xs[next_node] - my_x, + self.node_ys[next_node] - my_y, + ] + + # go find the edge. + + edge = self.circuit_graph.edges.get((inner_node, next_node)) + if edge is None: + edge = self.circuit_graph.edges.get((next_node, inner_node)) + if edge is None: + raise KeyError("uh oh") -class InternalPackParameter(object): - def __init__(self): - pass + # add this current to the loop. + edge["currents"].update( + {loop_currents[this_loop]: (inner_node, next_node)} + ) + inner_node = next_node + break + if this_loop == -1: + continue + del mcb[this_loop] diff --git a/test_pack.py b/test_pack.py deleted file mode 100644 index e29b6907a3..0000000000 --- a/test_pack.py +++ /dev/null @@ -1,10 +0,0 @@ -import pybamm -import numpy as np - -model = pybamm.lithium_ion.DFN(name="DFN") -parameter_values = model.default_parameter_values -parameter_values.update({"Current function [A]":pybamm.PsuedoInputParameter("cell_current")}) -sim = pybamm.Simulation(model, parameter_values=parameter_values) -sim.build() - - From 1cbc59d08a96cb836defc982f13b1c3aa0a6a374 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Fri, 21 Oct 2022 14:01:38 -0400 Subject: [PATCH 131/163] inbounds --- pybamm/expression_tree/operations/evaluate_julia.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 549de70587..02f7a2788c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -510,21 +510,21 @@ def write_function_easy(self, funcname, inline=True): self._function_string = self._function_string.replace(top_var_name, "J") self._function_string += "end\nend\nend" self._function_string = ( - "function {}(J, y, p, t)\n".format(funcname + "_with_consts") + "@inbounds function {}(J, y, p, t)\n".format(funcname + "_with_consts") + self._function_string ) elif self._dae_type == "semi-explicit": self._function_string = self._function_string.replace(top_var_name, "dy") self._function_string += "end\nend\nend" self._function_string = ( - "function {}(dy, y, p, t)\n".format(funcname + "_with_consts") + "@inbounds function {}(dy, y, p, t)\n".format(funcname + "_with_consts") + self._function_string ) elif self._dae_type == "implicit": self._function_string = self._function_string.replace(top_var_name, "out") self._function_string += "end\nend\nend" self._function_string = ( - "function {}(out, dy, y, p, t)\n".format(funcname + "_with_consts") + "@inbounds function {}(out, dy, y, p, t)\n".format(funcname + "_with_consts") + self._function_string ) return 0 From 1cfa50bb212077faec883e87e2d07c669a0eed15 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Oct 2022 18:05:33 +0000 Subject: [PATCH 132/163] style: pre-commit fixes --- pybamm/expression_tree/operations/evaluate_julia.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 02f7a2788c..daf11462ea 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -524,7 +524,9 @@ def write_function_easy(self, funcname, inline=True): self._function_string = self._function_string.replace(top_var_name, "out") self._function_string += "end\nend\nend" self._function_string = ( - "@inbounds function {}(out, dy, y, p, t)\n".format(funcname + "_with_consts") + "@inbounds function {}(out, dy, y, p, t)\n".format( + funcname + "_with_consts" + ) + self._function_string ) return 0 From d266fbf7b97a8eefa704fe8c729e397b2fc0eb70 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 24 Oct 2022 10:44:45 -0400 Subject: [PATCH 133/163] pack --- .../expression_tree/operations/build_pack.py | 142 +++++++++++------- 1 file changed, 86 insertions(+), 56 deletions(-) diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index 164053735c..06eebb7c46 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -1,6 +1,14 @@ # # convert an expression tree into a pack model # + +#TODO +# - Build Batteries +# -- Current In Batteries +# -- Terminal voltage from batteries +# - Test sign convention +# - Eliminate node1x & node1y (use graph only) +# - Thermals import pybamm from copy import deepcopy import networkx as nx @@ -23,6 +31,7 @@ def __init__(self, model, netlist, parameter_values=None): {"Current function [A]": pybamm.PsuedoInputParameter("cell_current")} ) self.cell_parameter_values = parameter_values + sim = pybamm.Simulation(model, parameter_values=parameter_values) sim.build() self.cell_model = pybamm.numpy_concatenation( @@ -48,21 +57,15 @@ def __init__(self, model, netlist, parameter_values=None): def process_netlist(self): curr = [{} for i in range(len(self.netlist))] self.netlist.insert(0, "currents", curr) + self.netlist = self.netlist.rename( columns={"node1": "source", "node2": "target"} ) + self.netlist["positive_node"] = self.netlist["source"] + self.netlist["negative_node"] = self.netlist["target"] self.circuit_graph = nx.from_pandas_edgelist(self.netlist, edge_attr=True) - def repeat_cells(self, num_cells): - for n in range(num_cells - 1): - self.add_new_cell() - self.offset += self.cell_size - self._sv_done = [] - print("adding cell {} of {}".format(n, num_cells)) - self.built_model = pybamm.NumpyConcatenation(*self._cells) - self.built_model.set_id() - print("done building pack") - + #Function that adds new cells, and puts them in the appropriate places. def add_new_cell(self): # TODO: deal with variables dict here too. # This is the place to get clever. @@ -104,19 +107,25 @@ def build_pack(self): # generate loop currents and current source voltages-- this is what we don't know. num_loops = len(mcb) - num_curr_sources = sum([desc[0] == "I" for desc in self.netlist.desc]) + + curr_sources = [edge for edge in self.circuit_graph.edges if self.circuit_graph.edges[edge]["desc"][0]=="I"] + num_curr_sources = len(curr_sources) loop_currents = [ pybamm.StateVector(slice(n, n + 1), name="current_{}".format(n)) for n in range(num_loops) ] - curr_source_voltages = [ - pybamm.StateVector(slice(n, n + 1), name="current_source_{}".format(n)) - for n in range(num_loops, num_loops + num_curr_sources) - ] + + curr_sources = [] + n = num_loops + for edge in self.circuit_graph.edges: + if self.circuit_graph.edges[edge]["desc"][0] == "I": + self.circuit_graph.edges[edge]["voltage"] = pybamm.StateVector(slice(n, n + 1), name="current_source_{}".format(n)) + n += 1 + curr_sources.append(edge) # now we know the offset, we should "build" the batteries here. will still need to replace the currents later. - self.offset = len(loop_currents) + len(curr_source_voltages) + self.offset = len(loop_currents) + len(curr_sources) self.batteries = {} for desc in self.netlist.desc: if desc[0] == "V": @@ -124,56 +133,64 @@ def build_pack(self): self.batteries.update({desc: new_cell}) self.offset += self.cell_size - if len(curr_source_voltages) != 1: + if len(curr_sources) != 1: raise NotImplementedError("can't do this yet") # copy the basis which we can use to place the loop currents basis_to_place = deepcopy(mcb) self.place_currents(loop_currents, basis_to_place) - def build_pack_equations(self, loop_currents, curr_source_voltages): + pack_eqs = self.build_pack_equations(loop_currents, curr_sources) + + + + + + + def build_pack_equations(self, loop_currents, curr_sources): # start by looping through the loop currents. Sum Voltages + pack_equations = [] for i, loop_current in enumerate(loop_currents): # loop through the edges eq = [] for edge in self.circuit_graph.edges: - if loop_current in edge["currents"]: - this_edge_equation = [] - edge_type = edge["desc"][0] - positive_direction = edge["currents"][loop_current] + if loop_current in self.circuit_graph.edges[edge]["currents"]: + #get the name of the edge current. + edge_type = self.circuit_graph.edges[edge]["desc"][0] + direction = self.circuit_graph.edges[edge]["currents"][loop_current] this_edge_current = loop_current - for current in edge["currents"]: + for current in self.circuit_graph.edges[edge]["currents"]: if current == loop_current: continue - if edge["currents"][current] == positive_direction: + if self.circuit_graph.edges[edge]["currents"][current] == "positive": this_edge_current = this_edge_current + current else: this_edge_current = this_edge_current - current if edge_type == "R": - eq.append(this_edge_current * edge["value"]) + eq.append(this_edge_current * self.circuit_graph.edges[edge]["value"]) elif edge_type == "I": - curr_source_num = edge["desc"][1:] + curr_source_num = self.circuit_graph.edges[edge]["desc"][1:] if curr_source_num != "0": raise NotImplementedError( "multiple current sources is not yet supported" ) - # need to check sign here. - eq.append(curr_source_voltages[0]) - elif edge_type == "V": - # voltage sources always point up. - # note that right now this is actually - # just a battery, not a voltage - voltage = self.batteries[edge["desc"]] - if ( - self.node_ys[positive_direction[0]] - > self.node_ys[positive_direction[1]] - ): - # voltage source is negative. - eq.append(-voltage) + + if direction == "positive": + eq.append(self.circuit_graph.edges[edge]["voltage"]) else: + eq.append(-self.circuit_graph.edges[edge]["voltage"]) + elif edge_type == "V": + # + voltage = self.batteries[self.circuit_graph.edges[edge]["desc"]] + if direction =="positive": eq.append(voltage) + else: + eq.append(-voltage) + if len(eq) == 0: - raise NotImplementedError("uh oh") + raise NotImplementedError( + "packs must include at least 1 circuit element" + ) elif len(eq) == 1: expr = eq[0] else: @@ -181,8 +198,28 @@ def build_pack_equations(self, loop_currents, curr_source_voltages): for e in range(2, len(eq)): expr = expr + eq[e] # add equation to the pack. + pack_equations.append(expr) # then loop through the current source voltages. Sum Currents. + for i,curr_source in enumerate(curr_sources): + currents = list(self.circuit_graph.edges[curr_source]["currents"]) + if self.circuit_graph.edges[curr_source]["currents"][currents[0]] == "positive": + expr = currents[0] + else: + expr = -currents[0] + for current in currents[1:]: + if self.circuit_graph.edges[curr_source]["currents"][current] == "positive": + expr = expr+current + else: + expr = expr-current + pack_equations.append(expr) + + #concatenate all the pack equations and return it. + pack_eqs = pybamm.numpy_concatenation(*pack_equations) + return pack_eqs + + + # This function places the currents on the edges in a predefined order. # it begins at loop 0, and loops through each "loop" -- really a cycle @@ -192,20 +229,11 @@ def build_pack_equations(self, loop_currents, curr_source_voltages): # loop and then proceeds to the next node and does the same thing. It # loops until all the loop currents have been placed. def place_currents(self, loop_currents, mcb): - for node in sorted(self.circuit_graph.nodes): - if mcb == []: - # loops are all done! - break - this_loop = -1 - for i, loop in enumerate(mcb): - # in each loop use x and y to figure out direction. - # Within the same loop, go right first. - if node not in loop: - continue + bottom_loop = 0 + for this_loop, loop in enumerate(mcb): + for node in sorted(self.circuit_graph.nodes): if node in loop: - # print("starting loop {}".format(loop)) # setting var to remove the loop later - this_loop = i done_nodes = set() # doesn't actually matter where we start. # loop will always be a set. @@ -265,11 +293,13 @@ def place_currents(self, loop_currents, mcb): raise KeyError("uh oh") # add this current to the loop. + if inner_node == edge["positive_node"]: + direction = "negative" + else: + direction = "positive" + edge["currents"].update( - {loop_currents[this_loop]: (inner_node, next_node)} + {loop_currents[this_loop]: direction} ) inner_node = next_node break - if this_loop == -1: - continue - del mcb[this_loop] From 7687702331ef80b90bd100a9f1380f16be49a53f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Mon, 24 Oct 2022 21:07:42 -0400 Subject: [PATCH 134/163] finishing up --- pybamm/__init__.py | 4 +- pybamm/expression_tree/input_parameter.py | 8 -- .../expression_tree/operations/build_pack.py | 96 +++++++++++++++---- .../operations/evaluate_julia.py | 2 + 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 53d7d6e640..e913b27f28 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -71,7 +71,7 @@ from .expression_tree.broadcasts import * from .expression_tree.functions import * from .expression_tree.interpolant import Interpolant -from .expression_tree.input_parameter import InputParameter, PsuedoInputParameter +from .expression_tree.input_parameter import InputParameter from .expression_tree.parameter import Parameter, FunctionParameter from .expression_tree.scalar import Scalar from .expression_tree.variable import * @@ -98,7 +98,7 @@ from .expression_tree.operations.unpack_symbols import SymbolUnpacker from .expression_tree.operations.replace_symbols import SymbolReplacer from .expression_tree.operations.evaluate_julia import JuliaConverter -from .expression_tree.operations.build_pack import Pack +from .expression_tree.operations.build_pack import Pack, PsuedoInputParameter # # Model classes diff --git a/pybamm/expression_tree/input_parameter.py b/pybamm/expression_tree/input_parameter.py index 2f703a0ef9..62c08bf0fd 100644 --- a/pybamm/expression_tree/input_parameter.py +++ b/pybamm/expression_tree/input_parameter.py @@ -101,11 +101,3 @@ def _base_evaluate(self, t=None, y=None, y_dot=None, inputs=None): self._expected_size ) ) - -class PsuedoInputParameter(InputParameter): - def create_copy(self): - """See :meth:`pybamm.Symbol.new_copy()`.""" - new_input_parameter = PsuedoInputParameter( - self.name, self.domain, expected_size=self._expected_size - ) - return new_input_parameter diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index 06eebb7c46..2fe9d870a3 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -3,9 +3,7 @@ # #TODO -# - Build Batteries # -- Current In Batteries -# -- Terminal voltage from batteries # - Test sign convention # - Eliminate node1x & node1y (use graph only) # - Thermals @@ -16,6 +14,32 @@ import pandas as pd import liionpack as lp +class PsuedoInputParameter(pybamm.InputParameter): + def create_copy(self): + """See :meth:`pybamm.Symbol.new_copy()`.""" + new_input_parameter = PsuedoInputParameter( + self.name, self.domain, expected_size=self._expected_size + ) + return new_input_parameter + + @property + def children(self): + return self._children + + @children.setter + def children(self, expr): + self._children = expr + + +def set_psuedo(symbol, expr): + if isinstance(symbol, PsuedoInputParameter): + symbol.children = [expr] + else: + for child in symbol.children: + set_psuedo(child, expr) + symbol.set_id() + + class Pack(object): def __init__(self, model, netlist, parameter_values=None): @@ -24,6 +48,7 @@ def __init__(self, model, netlist, parameter_values=None): # Build the cell expression tree with necessary parameters. # think about moving this to a separate function. + if parameter_values is not None: raise AssertionError("parameter values not supported") parameter_values = model.default_parameter_values @@ -31,7 +56,7 @@ def __init__(self, model, netlist, parameter_values=None): {"Current function [A]": pybamm.PsuedoInputParameter("cell_current")} ) self.cell_parameter_values = parameter_values - +# sim = pybamm.Simulation(model, parameter_values=parameter_values) sim.build() self.cell_model = pybamm.numpy_concatenation( @@ -39,10 +64,9 @@ def __init__(self, model, netlist, parameter_values=None): ) self.cell_size = self.cell_model.shape[0] self._sv_done = [] + self.built_model = sim.built_model self.netlist = netlist - self.cell_currents = {} - self.process_netlist() # get x and y coords for nodes from graph. @@ -99,6 +123,11 @@ def add_offset_to_state_vectors(self, symbol): child.set_id() symbol.set_id() + def get_new_terminal_voltage(self): + symbol = self.built_model.variables["Terminal voltage [V]"] + self.add_offset_to_state_vectors(symbol) + return symbol + def build_pack(self): # this function builds expression trees to compute the current. @@ -109,12 +138,14 @@ def build_pack(self): num_loops = len(mcb) curr_sources = [edge for edge in self.circuit_graph.edges if self.circuit_graph.edges[edge]["desc"][0]=="I"] - num_curr_sources = len(curr_sources) loop_currents = [ - pybamm.StateVector(slice(n, n + 1), name="current_{}".format(n)) + pybamm.StateVector(slice(n,n+1), name="current_{}".format(n)) for n in range(num_loops) ] + for loop_current in loop_currents: + print(loop_current.y_slices) + #print(loop_current.y_slices for loop_current in loop_currents) curr_sources = [] n = num_loops @@ -125,31 +156,41 @@ def build_pack(self): curr_sources.append(edge) # now we know the offset, we should "build" the batteries here. will still need to replace the currents later. - self.offset = len(loop_currents) + len(curr_sources) + self.offset = 0 self.batteries = {} + cells = [] for desc in self.netlist.desc: if desc[0] == "V": new_cell = self.add_new_cell() - self.batteries.update({desc: new_cell}) + cells.append(new_cell) + terminal_voltage = self.get_new_terminal_voltage() + self.batteries.update({desc: {"cell" : new_cell, "voltage" : terminal_voltage, "current_replaced" : False}}) self.offset += self.cell_size + cell_eqs = pybamm.numpy_concatenation(*cells) if len(curr_sources) != 1: raise NotImplementedError("can't do this yet") # copy the basis which we can use to place the loop currents basis_to_place = deepcopy(mcb) - self.place_currents(loop_currents, basis_to_place) - pack_eqs = self.build_pack_equations(loop_currents, curr_sources) + + self.pack = pybamm.numpy_concatenation(pack_eqs, cell_eqs) + self.ics = self.initialize_pack(num_loops, len(curr_sources)) - - + def initialize_pack(self, num_loops, num_curr_sources): + curr_ics = pybamm.Vector([1.0 for curr_source in range(num_loops)]) + curr_source_v_ics = pybamm.Vector([1.0 for curr_source in range(num_curr_sources)]) + cell_ics = pybamm.numpy_concatenation(*[self.built_model.concatenated_initial_conditions for n in range(len(self.batteries))]) + ics = pybamm.numpy_concatenation(*[curr_ics, curr_source_v_ics, cell_ics]) + return ics def build_pack_equations(self, loop_currents, curr_sources): # start by looping through the loop currents. Sum Voltages pack_equations = [] + cells = [] for i, loop_current in enumerate(loop_currents): # loop through the edges eq = [] @@ -157,6 +198,7 @@ def build_pack_equations(self, loop_currents, curr_sources): if loop_current in self.circuit_graph.edges[edge]["currents"]: #get the name of the edge current. edge_type = self.circuit_graph.edges[edge]["desc"][0] + desc = self.circuit_graph.edges[edge]["desc"] direction = self.circuit_graph.edges[edge]["currents"][loop_current] this_edge_current = loop_current for current in self.circuit_graph.edges[edge]["currents"]: @@ -180,12 +222,28 @@ def build_pack_equations(self, loop_currents, curr_sources): else: eq.append(-self.circuit_graph.edges[edge]["voltage"]) elif edge_type == "V": - # - voltage = self.batteries[self.circuit_graph.edges[edge]["desc"]] + #first, check and see if the battery has been done yet. + if not self.batteries[self.circuit_graph.edges[edge]["desc"]]["current_replaced"]: + currents = list(self.circuit_graph.edges[edge]["currents"]) + + if self.circuit_graph.edges[edge]["currents"][currents[0]] == "positive": + expr = currents[0] + else: + expr = -currents[0] + for current in currents[1:]: + if self.circuit_graph.edges[edge]["currents"][current] == "positive": + expr = expr+current + else: + expr = expr-current + set_psuedo(self.batteries[self.circuit_graph.edges[edge]["desc"]]["cell"], expr) + voltage = self.batteries[self.circuit_graph.edges[edge]["desc"]]["voltage"] if direction =="positive": eq.append(voltage) else: eq.append(-voltage) + #check to see if the battery input current has been replaced yet. + #If not, replace the current with the actual current. + if len(eq) == 0: raise NotImplementedError( @@ -203,11 +261,8 @@ def build_pack_equations(self, loop_currents, curr_sources): # then loop through the current source voltages. Sum Currents. for i,curr_source in enumerate(curr_sources): currents = list(self.circuit_graph.edges[curr_source]["currents"]) - if self.circuit_graph.edges[curr_source]["currents"][currents[0]] == "positive": - expr = currents[0] - else: - expr = -currents[0] - for current in currents[1:]: + expr = pybamm.Scalar(self.circuit_graph.edges[curr_source]["value"]) + for current in currents: if self.circuit_graph.edges[curr_source]["currents"][current] == "positive": expr = expr+current else: @@ -303,3 +358,4 @@ def place_currents(self, loop_currents, mcb): ) inner_node = next_node break + diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 549de70587..9283ad12e5 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -275,6 +275,8 @@ def _convert_tree_to_intermediate(self, symbol): elif isinstance(symbol, pybamm.Time): my_id = symbol.id self._intermediate[my_id] = JuliaTime(my_id) + elif isinstance(symbol, pybamm.PsuedoInputParameter): + my_id = self._convert_tree_to_intermediate(symbol.children[0]) elif isinstance(symbol, pybamm.InputParameter): my_id = symbol.id name = symbol.name From f41d5f2cd90cf019b0fa69e993c624a304b72113 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 25 Oct 2022 20:30:19 -0400 Subject: [PATCH 135/163] done --- .../expression_tree/operations/build_pack.py | 88 +++++++++++-------- .../operations/evaluate_julia.py | 11 ++- 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index 2fe9d870a3..b4895d746d 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -3,10 +3,10 @@ # #TODO -# -- Current In Batteries # - Test sign convention # - Eliminate node1x & node1y (use graph only) # - Thermals +# - FunctionPack import pybamm from copy import deepcopy import networkx as nx @@ -14,6 +14,38 @@ import pandas as pd import liionpack as lp +class offsetter(object): + def __init__(self, offset): + self._sv_done = [] + self.offset = offset + + + def add_offset_to_state_vectors(self, symbol): + # this function adds an offset to the state vectors + new_y_slices = () + if isinstance(symbol, pybamm.StateVector): + # need to make sure its in place + if symbol.id not in self._sv_done: + for this_slice in symbol.y_slices: + start = this_slice.start + self.offset + stop = this_slice.stop + self.offset + step = this_slice.step + new_slice = slice(start, stop, step) + new_y_slices += (new_slice,) + symbol.replace_y_slices(*new_y_slices) + symbol.set_id() + self._sv_done += [symbol.id] + + elif isinstance(symbol, pybamm.StateVectorDot): + raise NotImplementedError("Idk what this means") + else: + for child in symbol.children: + self.add_offset_to_state_vectors(child) + child.set_id() + symbol.set_id() + + + class PsuedoInputParameter(pybamm.InputParameter): def create_copy(self): """See :meth:`pybamm.Symbol.new_copy()`.""" @@ -40,7 +72,6 @@ def set_psuedo(symbol, expr): symbol.set_id() - class Pack(object): def __init__(self, model, netlist, parameter_values=None): # this is going to be a work in progress for a while: @@ -77,6 +108,18 @@ def __init__(self, model, netlist, parameter_values=None): node_ys[row.node1] = row.node1_y self.node_xs = node_xs self.node_ys = node_ys + self.batt_string = None + + def lolz(self, Ns, Np): + if self.batt_string is None: + one_parallel = "" + for np in range(Np): + one_parallel += "🔋" + batt = "" + for ns in range(Ns): + batt += one_parallel + "\n" + self.batt_string = batt + print(self.batt_string) def process_netlist(self): curr = [{} for i in range(len(self.netlist))] @@ -95,37 +138,15 @@ def add_new_cell(self): # This is the place to get clever. new_model = deepcopy(self.cell_model) # at some point need to figure out parameters - self.add_offset_to_state_vectors(new_model) + my_offsetter = offsetter(self.offset) + my_offsetter.add_offset_to_state_vectors(new_model) new_model.set_id() return new_model - def add_offset_to_state_vectors(self, symbol): - # this function adds an offset to the state vectors - new_y_slices = () - if isinstance(symbol, pybamm.StateVector): - # need to make sure its in place - if symbol.id not in self._sv_done: - for this_slice in symbol.y_slices: - start = this_slice.start + self.offset - stop = this_slice.stop + self.offset - step = this_slice.step - new_slice = slice(start, stop, step) - new_y_slices += (new_slice,) - symbol.replace_y_slices(*new_y_slices) - symbol.set_id() - self._sv_done += [symbol.id] - - elif isinstance(symbol, pybamm.StateVectorDot): - raise NotImplementedError("Idk what this means") - else: - for child in symbol.children: - self.add_offset_to_state_vectors(child) - child.set_id() - symbol.set_id() - def get_new_terminal_voltage(self): - symbol = self.built_model.variables["Terminal voltage [V]"] - self.add_offset_to_state_vectors(symbol) + symbol = deepcopy(self.built_model.variables["Terminal voltage [V]"]) + my_offsetter = offsetter(self.offset) + my_offsetter.add_offset_to_state_vectors(symbol) return symbol def build_pack(self): @@ -143,8 +164,6 @@ def build_pack(self): pybamm.StateVector(slice(n,n+1), name="current_{}".format(n)) for n in range(num_loops) ] - for loop_current in loop_currents: - print(loop_current.y_slices) #print(loop_current.y_slices for loop_current in loop_currents) curr_sources = [] @@ -156,7 +175,7 @@ def build_pack(self): curr_sources.append(edge) # now we know the offset, we should "build" the batteries here. will still need to replace the currents later. - self.offset = 0 + self.offset = num_loops + len(curr_sources) self.batteries = {} cells = [] for desc in self.netlist.desc: @@ -174,6 +193,7 @@ def build_pack(self): basis_to_place = deepcopy(mcb) self.place_currents(loop_currents, basis_to_place) pack_eqs = self.build_pack_equations(loop_currents, curr_sources) + pack_eqs = pybamm.numpy_concatenation(*pack_eqs) self.pack = pybamm.numpy_concatenation(pack_eqs, cell_eqs) self.ics = self.initialize_pack(num_loops, len(curr_sources)) @@ -270,8 +290,7 @@ def build_pack_equations(self, loop_currents, curr_sources): pack_equations.append(expr) #concatenate all the pack equations and return it. - pack_eqs = pybamm.numpy_concatenation(*pack_equations) - return pack_eqs + return pack_equations @@ -358,4 +377,3 @@ def place_currents(self, loop_currents, mcb): ) inner_node = next_node break - diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 9283ad12e5..2d3c6f31a2 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -8,6 +8,11 @@ from math import floor import graphlib +class FunctionRepeat(object): + def __init__(self, expr, inputs): + pass + pass + def remove_lines_with(input_string, pattern): string_list = input_string.split("\n") @@ -1037,11 +1042,11 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): continue elif child_var.shape[0] == 1: start_row = end_row + 1 - end_row = start_row + 1 + end_row = start_row if converter._preallocate: if vec: - code += "{}[{}{} = {} \n".format( - my_name, start_row, right_parenthesis, child_var_name + code += "@. {}[{}:{}{} = {}\n".format( + my_name, start_row, start_row, right_parenthesis, child_var_name ) else: code += "@. {}[{}{} = {} \n".format( From dc21f5683c536dca64138f028d0bc8975fbc166b Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 25 Oct 2022 21:18:23 -0400 Subject: [PATCH 136/163] getting jl stuff --- pybamm/expression_tree/operations/build_pack.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index b4895d746d..a6f0444d42 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -93,6 +93,12 @@ def __init__(self, model, netlist, parameter_values=None): self.cell_model = pybamm.numpy_concatenation( sim.built_model.concatenated_rhs, sim.built_model.concatenated_algebraic ) + + self.timescale = sim.built_model.timescale + + self.len_cell_rhs = sim.built_model.len_rhs + self.len_cell_algebraic = self.built_model.len_algebraic + self.cell_size = self.cell_model.shape[0] self._sv_done = [] self.built_model = sim.built_model @@ -185,6 +191,7 @@ def build_pack(self): terminal_voltage = self.get_new_terminal_voltage() self.batteries.update({desc: {"cell" : new_cell, "voltage" : terminal_voltage, "current_replaced" : False}}) self.offset += self.cell_size + self.num_cells = len(cells) cell_eqs = pybamm.numpy_concatenation(*cells) if len(curr_sources) != 1: @@ -194,6 +201,7 @@ def build_pack(self): self.place_currents(loop_currents, basis_to_place) pack_eqs = self.build_pack_equations(loop_currents, curr_sources) pack_eqs = pybamm.numpy_concatenation(*pack_eqs) + self.len_pack_eqs = len(pack_eqs) self.pack = pybamm.numpy_concatenation(pack_eqs, cell_eqs) self.ics = self.initialize_pack(num_loops, len(curr_sources)) From 7be839bb9598d9dbe9b3c274a632801edd66a8a0 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 25 Oct 2022 21:38:06 -0400 Subject: [PATCH 137/163] typo :( --- pybamm/expression_tree/operations/build_pack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index a6f0444d42..6fd5d372fc 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -97,7 +97,7 @@ def __init__(self, model, netlist, parameter_values=None): self.timescale = sim.built_model.timescale self.len_cell_rhs = sim.built_model.len_rhs - self.len_cell_algebraic = self.built_model.len_algebraic + self.len_cell_algebraic = sim.built_model.len_algebraic self.cell_size = self.cell_model.shape[0] self._sv_done = [] From d9813a00ba1de8191515da8fae8dcf7fcc696207 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 25 Oct 2022 21:48:22 -0400 Subject: [PATCH 138/163] typo :( --- pybamm/expression_tree/operations/build_pack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index 6fd5d372fc..0edbf0bfd7 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -97,7 +97,7 @@ def __init__(self, model, netlist, parameter_values=None): self.timescale = sim.built_model.timescale self.len_cell_rhs = sim.built_model.len_rhs - self.len_cell_algebraic = sim.built_model.len_algebraic + self.len_cell_algebraic = sim.built_model.len_alg self.cell_size = self.cell_model.shape[0] self._sv_done = [] From 0f647e131933394477b6265d12c63453ce60c186 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Tue, 25 Oct 2022 21:53:01 -0400 Subject: [PATCH 139/163] typo :( --- pybamm/expression_tree/operations/build_pack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py index 0edbf0bfd7..eb1426a007 100644 --- a/pybamm/expression_tree/operations/build_pack.py +++ b/pybamm/expression_tree/operations/build_pack.py @@ -200,8 +200,9 @@ def build_pack(self): basis_to_place = deepcopy(mcb) self.place_currents(loop_currents, basis_to_place) pack_eqs = self.build_pack_equations(loop_currents, curr_sources) - pack_eqs = pybamm.numpy_concatenation(*pack_eqs) self.len_pack_eqs = len(pack_eqs) + pack_eqs = pybamm.numpy_concatenation(*pack_eqs) + self.pack = pybamm.numpy_concatenation(pack_eqs, cell_eqs) self.ics = self.initialize_pack(num_loops, len(curr_sources)) From c2d26f3106336d89c59769fb8b2884e0e6cf09cc Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 26 Oct 2022 11:39:26 -0400 Subject: [PATCH 140/163] deleted accidental file --- file.jl | 377 -------------------------------------------------------- 1 file changed, 377 deletions(-) delete mode 100644 file.jl diff --git a/file.jl b/file.jl deleted file mode 100644 index f580b933ef..0000000000 --- a/file.jl +++ /dev/null @@ -1,377 +0,0 @@ -begin -f = let -cache_1 = zeros(20) -const_1 = sparse([ 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, - 9,10, 9,10,11,10,11,12,11,12,13,12,13,14,13,14,15,14,15,16,15,16,17,16, - 17,18,17,18,19,18,19,20,19,20], [ 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, - 9, 9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16,16,16,17, - 17,17,18,18,18,19,19,19,20,20], Float64[-10576.1491769 , 1510.87845384, 10576.1491769 , -7554.39226921, - 2226.55772145, 6043.51381537, -7236.31259472, 2572.57682681, - 5009.75487327, -7146.04674115, 2774.07191525, 4573.46991433, - -7108.55928283, 2905.53548816, 4334.48736758, -7089.50659111, - 2997.96354621, 4183.97110295, -7078.52503966, 3066.45745366, - 4080.56149345, -7071.62637272, 3119.23293697, 4005.16891906, - -7067.01212281, 3161.13683885, 3947.77918585, -7063.77491151, - 3195.21123169, 3902.63807266, -7061.41682204, 3223.46108414, - 3866.20559035, -7059.64617602, 3247.26115453, 3836.18509187, - -7058.28292616, 3267.58539469, 3811.02177163, -7057.21105954, - 3285.14300899, 3789.62566485, -7056.35309584, 3300.46264189, - 3771.21008685, -7055.65569222, 3313.94637611, 3755.19305033, - -7055.08115228, 3325.90545389, 3741.13477616, -7054.60222572, - 3336.58455045, 3728.69677183, -7054.19881809, 3346.17866158, - 3717.61426763, -3346.17866158], 20, 20) -const_2 = [[ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [-26.29272568]] -cache_2 = zeros(20) -const_3 = sparse([ 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, - 9,10, 9,10,11,10,11,12,11,12,13,12,13,14,13,14,15,14,15,16,15,16,17,16, - 17,18,17,18,19,18,19,20,19,20], [ 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, - 9, 9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16,16,16,17, - 17,17,18,18,18,19,19,19,20,20], Float64[-27118.33122282, 3874.04731755, 27118.33122282,-19370.23658773, - 5709.1223627 , 15496.18927018,-18554.64767877, 6596.35083798, - 12845.52531607,-18323.19677217, 7113.0049109 , 11726.84593419, - -18227.07508419, 7450.09099528, 11114.07017329,-18178.22202848, - 7687.08601592, 10728.1310332 ,-18150.06420425, 7862.71141963, - 10462.97818833,-18132.37531466, 7998.03317171, 10269.66389503, - -18120.54390465, 8105.47907398, 10122.51073294,-18112.24336284, - 8192.84931203, 10006.76428886,-18106.19697958, 8265.28483114, - 9913.34766756,-18101.65686158, 8326.31065263, 9836.37203044, - -18098.16134913, 8378.42408895, 9771.85069649,-18095.41297318, - 8423.44361279, 9716.98888423,-18093.21306625, 8462.72472279, - 9669.76945346,-18091.42485184, 8497.29840029, 9628.70012904, - -18089.9516725 , 8527.96270228, 9592.65327221,-18088.7236557 , - 8555.34500116, 9560.76095342,-18087.68927715, 8579.9452861 , - 9532.34427598, -8579.9452861 ], 20, 20) -const_4 = [[ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [15.39019111]] -cache_3 = zeros(60) -const_5 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, - 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, - 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, - 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60,61], Float64[ -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45.], 60, 61) -cache_4 = zeros(61) -cache_5 = zeros(61) -const_6 = sparse([ 1, 1,61,61], [ 1, 2,59,60], Float64[ 1.5,-0.5,-0.5, 1.5], 61, 60) -cache_6 = zeros(60) -const_7 = [[-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-1.91554083] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548] - [-0.31475548]] -cache_7 = zeros(60) -cache_8 = zeros(61) -const_8 = sparse([ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, - 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, - 50,51,52,53,54,55,56,57,58,59,60], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 61, 59) -cache_9 = zeros(59) -cache_10 = zeros(59) -const_9 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], [ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) -cache_11 = zeros(59) -const_10 = sparse([ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, - 25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, - 49,50,51,52,53,54,55,56,57,58,59], [ 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, - 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49, - 50,51,52,53,54,55,56,57,58,59,60], Float64[1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1., - 1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.], 59, 60) -cache_12 = zeros(59) -const_11 = sparse([ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12, - 13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24, - 25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36, - 37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48, - 49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.2,0.8,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.8,0.2,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, - 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5], 59, 60) -cache_13 = zeros(61) -const_12 = sparse([ 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13,13, - 14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25, - 26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37, - 38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49,49, - 50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60], [ 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, - 13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25, - 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,47,47,48,48,49, - 49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60], Float64[ -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -72., 72.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180.,-180., 180., - -180., 180.,-180., 180.,-180., 180., -72., 72., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., -45., 45., - -45., 45., -45., 45., -45., 45., -45., 45., -45., 45.], 61, 60) -cache_14 = zeros(60) -const_13 = [[ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.18068626] - [ 0.1355147 ] - [ 0.18068626] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [-0.18068626] - [-0.1355147 ] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626] - [-0.18068626]] -const_14 = [[ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 56.21233949] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [ 0. ] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949] - [-56.21233949]] -const_15 = [] - -function f_with_consts(dy, y, p, t) - -mul!(cache_1,const_1,(@view y[1:20])) -mul!(cache_2,const_3,(@view y[21:40])) -@. cache_7[1:20] = ((@view y[41:60]) * 3333.3333333333335) -@. cache_7[21:40] = ((@view y[61:80]) * 1000.0) -@. cache_7[41:60] = ((@view y[81:100]) * 3333.3333333333335) -@. cache_6 = const_7 * (exp((-0.00065 * max(cache_7,10.0)))) -mul!(cache_5,const_6,cache_6) -mul!(cache_10,const_9,cache_6) -mul!(cache_11,const_10,cache_6) -mul!(cache_12,const_11,cache_6) -@. cache_9 = (cache_10 * cache_11) / (cache_12 + 1e-16) -mul!(cache_8,const_8,cache_9) -@. cache_14[1:20] = ((@view y[41:60]) / 0.3) -@. cache_14[21:40] = (@view y[61:80]) -@. cache_14[41:60] = ((@view y[81:100]) / 0.3) -mul!(cache_13,const_12,cache_14) -@. cache_4 = (cache_5 + cache_8) * cache_13 -mul!(cache_3,const_5,cache_4) -@. dy[1:20] = (cache_1 + const_2) -@. dy[21:40] = (cache_2 + const_4) -@. dy[41:100] = (((cache_3 + const_13) / -0.008035880643729006) + const_14) - - return nothing -end -end -end \ No newline at end of file From 725ae87ed38edd975419217899a7307806f388ed Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 26 Oct 2022 12:12:58 -0400 Subject: [PATCH 141/163] add cache name --- .../operations/evaluate_julia.py | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index daf11462ea..15392793a0 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -29,7 +29,12 @@ def __init__( input_parameter_order=None, inline=True, parallel="legacy-serial", + outputs = [], + inputs = [] ): + #if len(outputs) != 1: + # raise NotImplementedError("Julia black box can only have 1 output") + if ismtk: raise NotImplementedError("mtk is not supported") @@ -335,12 +340,14 @@ def find_broadcastable_shape(self, id_left, id_right): # between the two. So, we have to do that with an if statement. # Cache and Const Creation - def create_cache(self, symbol): + def create_cache(self, symbol, cache_name=None): my_id = symbol.output cache_shape = self._intermediate[my_id].shape cache_id = self._cache_id self._cache_id += 1 - cache_name = "cache_{}".format(cache_id) + if cache_name is None: + cache_name = "cache_{}".format(cache_id) + if self._preallocate: if self._cache_type == "standard": if cache_shape[1] == 1: @@ -401,11 +408,14 @@ def create_cache(self, symbol): self._cache_dict[symbol.output] = cache_name return self._cache_dict[symbol.output] - def create_const(self, symbol): + def create_const(self, symbol, cache_name=None): my_id = symbol.output const_id = self._const_id + 1 self._const_id = const_id - const_name = "const_{}".format(const_id) + if cache_name is None: + const_name = "const_{}".format(const_id) + else: + const_name = cache_name self._const_dict[my_id] = const_name mat_value = symbol.value val_line = self.write_const(mat_value) @@ -591,10 +601,10 @@ def __init__(self, left_input, right_input, output, shape): self.output = output self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False, cache_name=None): if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] - result_var_name = converter.create_cache(self) + result_var_name = converter.create_cache(self, cache_name = cache_name) left_input_var_name, right_input_var_name = self.get_binary_inputs( converter, inline=False ) @@ -621,12 +631,12 @@ def __init__(self, left_input, right_input, output, shape, operator): self.shape = shape self.operator = operator - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: - result_var_name = converter.create_cache(self) + result_var_name = converter.create_cache(self, cache_name=cache_name) left_input_var_name, right_input_var_name = self.get_binary_inputs( converter, inline=True ) @@ -686,7 +696,7 @@ def __init__(self, left_input, right_input, output, shape, name): self.shape = shape self.name = name - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] inline = inline & converter._inline @@ -741,12 +751,12 @@ def generate_code_and_dag(self, converter: JuliaConverter, code): if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: - result_var_name = converter.create_cache(self) + result_var_name = converter.create_cache(self, cache_name=cache_name) input_var_name = converter._intermediate[ self.input ]._convert_intermediate_to_code(converter, inline=True) @@ -769,13 +779,13 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): class JuliaNegation(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] inline = inline & converter._inline if not inline: - result_var_name = converter.create_cache(self) + result_var_name = converter.create_cache(self, cache_name=cache_name) input_var_name = converter._intermediate[ self.input ]._convert_intermediate_to_code(converter, inline=True) @@ -793,10 +803,10 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): class JuliaMinimumMaximum(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] - result_var_name = converter.create_cache(self) + result_var_name = converter.create_cache(self, cache_name=cache_name) input_var_name = converter._intermediate[ self.input ]._convert_intermediate_to_code(converter, inline=False) @@ -823,7 +833,7 @@ def generate_code_and_dag(self, converter: JuliaConverter, code): if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] index = self.index @@ -902,8 +912,8 @@ def __init__(self, my_id, value): self.value = value self.shape = value.shape - def _convert_intermediate_to_code(self, converter, inline=True): - converter.create_const(self) + def _convert_intermediate_to_code(self, converter, inline=True, cache_name=None): + converter.create_const(self, cache_name=cache_name) self.generate_code_and_dag(converter) return converter._const_dict[self.output] @@ -914,7 +924,7 @@ def __init__(self, my_id, loc, shape): self.loc = loc self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): start = self.loc[0] + 1 end = self.loc[1] self.generate_code_and_dag(converter) @@ -925,7 +935,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): class JuliaStateVectorDot(JuliaStateVector): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): start = self.loc[0] + 1 end = self.loc[1] self.generate_code_and_dag(converter) @@ -941,7 +951,7 @@ def __init__(self, my_id, value): self.value = float(value) self.shape = (1, 1) - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): self.generate_code_and_dag(converter) return self.value @@ -951,7 +961,7 @@ def __init__(self, my_id): self.output = my_id self.shape = (1, 1) - def _convert_intermediate_to_code(self, converter, inline=True): + def _convert_intermediate_to_code(self, converter, inline=True, cache_name=None): self.generate_code_and_dag(converter) return "t" @@ -962,7 +972,7 @@ def __init__(self, my_id, name): self.shape = (1, 1) self.name = name - def _convert_intermediate_to_code(self, converter, inline=True): + def _convert_intermediate_to_code(self, converter, inline=True, cache_name=None): self.generate_code_and_dag(converter) return self.name @@ -981,11 +991,11 @@ def generate_code_and_dag(self, converter: JuliaConverter, code): if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, self.children): return converter._cache_dict[self.output] num_cols = self.shape[1] - my_name = converter.create_cache(self) + my_name = converter.create_cache(self, cache_name=cache_name) # assume we don't have tensors. Already asserted # concatenations have to have the same width. @@ -1085,11 +1095,11 @@ def __init__( self.secondary_dimension_npts = secondary_dimension_npts self.children_slices = children_slices - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True): + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): if converter.cache_exists(self.output, self.children): return converter._cache_dict[self.output] num_cols = self.shape[1] - result_var_name = converter.create_cache(self) + result_var_name = converter.create_cache(self, cache_name=cache_name) # assume we don't have tensors. Already asserted # that concatenations have to have the same width. From a89e4f7721684ff604d60423877639f52a174d23 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 26 Oct 2022 13:24:41 -0400 Subject: [PATCH 142/163] Julia Black Box --- .../operations/evaluate_julia.py | 89 ++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 15392793a0..c6673c10bf 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -30,7 +30,8 @@ def __init__( inline=True, parallel="legacy-serial", outputs = [], - inputs = [] + inputs = [], + black_box = False ): #if len(outputs) != 1: # raise NotImplementedError("Julia black box can only have 1 output") @@ -56,6 +57,7 @@ def __init__( self._type = "Float64" self._inline = inline self._parallel = parallel + self._black_box = black_box # "Caches" # Stores Constants to be Declared in the initial cache # insight: everything is just a line of code @@ -483,6 +485,52 @@ def write_function(self): else: raise NotImplementedError() + def write_black_box(self, funcname): + top = self._intermediate[next(reversed(self._intermediate))] + if len(self.outputs) != 1: + raise NotImplementedError( + "only 1 output is allowed!" + ) + if not self._preallocate: + raise NotImplementedError( + "black box only supports preallocation." + ) + #this will automatically be in place with the correct function name + top_var_name = top._convert_intermediate_to_code(self, inline=False, cache_name=self.outputs[0]) + + #still need to write the function. + self.write_function() + self._cache_and_const_string = ( + "begin\n{} = let \n".format(funcname) + self._cache_and_const_string + ) + + #No need to write a cache since it's in the input. + self._cache_and_const_string = remove_lines_with( + self._cache_and_const_string, top_var_name + ) + + #may need to modify this logic a bit in the future. + if "p" in self.inputs: + parameter_string = "" + for parameter in self.input_parameter_order: + parameter_string += "{},".format(parameter) + parameter_string += "= p\n" + self._function_string = parameter_string + self._function_string + + #same as _easy + self._function_string = ( + self._cache_initialization_string + self._function_string + ) + + #no support for not preallocating. (for now) + self._function_string += "\n return nothing\nend\nend\nend" + header_string = "@inbounds function(" + top_var_name + for this_input in self.inputs: + header_string = header_string + "," + this_input + header_string +=")\n" + self._function_string = header_string + self._function_string + return 0 + # Just get something working here, so can start actual testing def write_function_easy(self, funcname, inline=True): # start with the closure @@ -562,10 +610,47 @@ def convert_tree_to_intermediate(self, symbol, len_rhs=None): # rework this at some point def build_julia_code(self, funcname="f", inline=True): # get top node of tree - self.write_function_easy(funcname, inline=inline) + if self._black_box: + self.write_black_box(funcname) + else: + self.write_function_easy(funcname, inline=inline) string = self._cache_and_const_string + self._function_string return string +#this is a bit of a weird one, may change it at some point +class JuliaBlackBox(object): + def __init__(self, inputs, output, shape, name): + self.inputs = inputs + self.output = output + self.shape = shape + self.name = name + + def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False, cache_name=None): + if converter.cache_exists(self.output, self.inputs): + return converter._cache_dict[self.output] + result_var_name = converter.create_cache(self, cache_name = cache_name) + + input_var_names = [] + for input in self.inputs: + input_var_names.append( + converter._intermediate[input]._convert_intermediate_to_code(converter, inline=inline) + ) + result_var_name = converter._cache_dict[self.output] + code = "{}({}".format(self.name, self.output) + for input in input_var_names: + code = code+","+input + code = code+")\n" + + #black box always generates a cache. + self.generate_code_and_dag(converter, code) + return result_var_name + + def generate_code_and_dag(self, converter, code): + converter._code[self.output] = code + converter._dag[self.output] = set(self.inputs) + if converter._parallel == "legacy-serial": + converter._function_string += code + # BINARY OPERATORS: NEED TO DEFINE ONE FOR EACH MULTIPLE DISPATCH class JuliaBinaryOperation(object): From 0a2472612f0a1f61fa9d025cf839ef8e8ce030f7 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 09:21:43 -0400 Subject: [PATCH 143/163] black box --- pybamm/__init__.py | 1 + .../operations/evaluate_julia.py | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 188ded5945..3642848422 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -101,6 +101,7 @@ from .expression_tree.operations.replace_symbols import SymbolReplacer from .expression_tree.operations.evaluate_julia import ( JuliaConverter, + BlackBox ) # diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c6673c10bf..1a17939425 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -17,6 +17,29 @@ def remove_lines_with(input_string, pattern): my_string = my_string + s + "\n" return my_string +#Wrapper to designate a function. +#Bottom needs to already have a julia +#conversion. (for shape.) +class BlackBox(object): + def __init__(self, jl_shape, children, expr, name): + self.jl_shape = jl_shape + self.children = children + self.expr = expr + self.name = name + def set_id(self): + """ + Set the immutable "identity" of a variable (e.g. for identifying y_slices). + + Hashing can be slow, so we set the id when we create the node, and hence only + need to hash once. + """ + self._id = hash( + (self.__class__, self.name) + + tuple([child.id for child in self.children]) + ) + + + class JuliaConverter(object): def __init__( @@ -150,6 +173,20 @@ def _convert_tree_to_intermediate(self, symbol): symbol.secondary_dimensions_npts, symbol._children_slices, ) + elif isinstance(symbol, BlackBox): + my_id = symbol.id + child_ids = [] + for child in symbol.children: + child_id = self._convert_intermediate_to_code(child) + child_ids.append(child_id) + shape = symbol.jl_shape + name = symbol.name + self._intermediate[my_id] = JuliaBlackBox( + child_ids, + my_id, + shape, + name + ) elif isinstance(symbol, pybamm.MatrixMultiplication): # Break down the binary tree id_left, id_right, my_id = self.break_down_binary(symbol) From 54661ca2979363a6d9733d814684761553b11779 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 12:33:55 -0400 Subject: [PATCH 144/163] finish black box --- pybamm/__init__.py | 2 +- .../operations/evaluate_julia.py | 57 +++++++++++-------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 3642848422..67f2788748 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -101,7 +101,7 @@ from .expression_tree.operations.replace_symbols import SymbolReplacer from .expression_tree.operations.evaluate_julia import ( JuliaConverter, - BlackBox + PybammJuliaFunction ) # diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 1a17939425..931e38411e 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -20,23 +20,17 @@ def remove_lines_with(input_string, pattern): #Wrapper to designate a function. #Bottom needs to already have a julia #conversion. (for shape.) -class BlackBox(object): - def __init__(self, jl_shape, children, expr, name): - self.jl_shape = jl_shape - self.children = children +class PybammJuliaFunction(pybamm.Symbol): + def __init__(self, children, expr, name): self.expr = expr - self.name = name - def set_id(self): - """ - Set the immutable "identity" of a variable (e.g. for identifying y_slices). - - Hashing can be slow, so we set the id when we create the node, and hence only - need to hash once. - """ - self._id = hash( - (self.__class__, self.name) - + tuple([child.id for child in self.children]) - ) + super().__init__(name, children) + def evaluate_for_shape(self): + return self.expr.evaluate_for_shape() + + @property + def shape(self): + return self.expr.shape + @@ -173,15 +167,15 @@ def _convert_tree_to_intermediate(self, symbol): symbol.secondary_dimensions_npts, symbol._children_slices, ) - elif isinstance(symbol, BlackBox): + elif isinstance(symbol, PybammJuliaFunction): my_id = symbol.id child_ids = [] for child in symbol.children: - child_id = self._convert_intermediate_to_code(child) + child_id = self._convert_tree_to_intermediate(child) child_ids.append(child_id) - shape = symbol.jl_shape + shape = symbol.shape name = symbol.name - self._intermediate[my_id] = JuliaBlackBox( + self._intermediate[my_id] = JuliaJuliaFunction( child_ids, my_id, shape, @@ -561,7 +555,7 @@ def write_black_box(self, funcname): #no support for not preallocating. (for now) self._function_string += "\n return nothing\nend\nend\nend" - header_string = "@inbounds function(" + top_var_name + header_string = "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name for this_input in self.inputs: header_string = header_string + "," + this_input header_string +=")\n" @@ -628,7 +622,16 @@ def write_function_easy(self, funcname, inline=True): # this function will be the top level. def convert_tree_to_intermediate(self, symbol, len_rhs=None): - if self._dae_type == "implicit": + if isinstance(symbol, pybamm.PybammJuliaFunction): + #need to hash this out a bit more. + self._black_box = True + self.outputs = ["out"] + self.inputs = [] + self.funcname = symbol.name + #process inputs: input types can be StateVectors, + #StateVectorDots, parameters, time, and psuedo + # parameters. + elif self._dae_type == "implicit": symbol_minus_dy = [] end = 0 for child in symbol.orphans: @@ -641,13 +644,17 @@ def convert_tree_to_intermediate(self, symbol, len_rhs=None): else: symbol_minus_dy.append(child) symbol = pybamm.numpy_concatenation(*symbol_minus_dy) - self._convert_tree_to_intermediate(symbol) + if isinstance(symbol, pybamm.PybammJuliaFunction): + self._convert_tree_to_intermediate(symbol.expr) + else: + self._convert_tree_to_intermediate(symbol) return 0 # rework this at some point def build_julia_code(self, funcname="f", inline=True): # get top node of tree if self._black_box: + funcname = self.funcname self.write_black_box(funcname) else: self.write_function_easy(funcname, inline=inline) @@ -655,7 +662,7 @@ def build_julia_code(self, funcname="f", inline=True): return string #this is a bit of a weird one, may change it at some point -class JuliaBlackBox(object): +class JuliaJuliaFunction(object): def __init__(self, inputs, output, shape, name): self.inputs = inputs self.output = output @@ -673,7 +680,7 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False, converter._intermediate[input]._convert_intermediate_to_code(converter, inline=inline) ) result_var_name = converter._cache_dict[self.output] - code = "{}({}".format(self.name, self.output) + code = "{}({}".format(self.name, result_var_name) for input in input_var_names: code = code+","+input code = code+")\n" From 82321a3e7f9a2638b547805aa3744728e78ce20c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 15:45:23 -0400 Subject: [PATCH 145/163] Psuedo --- .../operations/evaluate_julia.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 931e38411e..c380b4e450 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -9,6 +9,34 @@ import graphlib + + +class PsuedoInputParameter(pybamm.InputParameter): + def create_copy(self): + """See :meth:`pybamm.Symbol.new_copy()`.""" + new_input_parameter = PsuedoInputParameter( + self.name, self.domain, expected_size=self._expected_size + ) + return new_input_parameter + + @property + def children(self): + return self._children + + @children.setter + def children(self, expr): + self._children = expr + + +def set_psuedo(symbol, expr): + if isinstance(symbol, PsuedoInputParameter): + symbol.children = [expr] + else: + for child in symbol.children: + set_psuedo(child, expr) + symbol.set_id() + + def remove_lines_with(input_string, pattern): string_list = input_string.split("\n") my_string = "" From 5fbbfd1b05a8b856465b3f62bfb9ec443a097b45 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 15:51:22 -0400 Subject: [PATCH 146/163] fix have julia once and for all --- pybamm/util.py | 26 ------------------- .../test_operations/test_evaluate_julia.py | 2 +- tests/unit/test_util.py | 11 -------- 3 files changed, 1 insertion(+), 38 deletions(-) diff --git a/pybamm/util.py b/pybamm/util.py index 5821757026..454ebe6f65 100644 --- a/pybamm/util.py +++ b/pybamm/util.py @@ -15,7 +15,6 @@ import timeit from platform import system import difflib -from julia.api import Julia, JuliaInfo, JuliaError import numpy as np import pkg_resources @@ -288,31 +287,6 @@ def get_parameters_filepath(path): return os.path.join(pybamm.__path__[0], path) -def have_julia(): - """ - Checks whether the Julia programming language has been installed - """ - - # Try fetching info about julia - try: - info = JuliaInfo.load() - except (FileNotFoundError, subprocess.CalledProcessError): - return False - - # Compatibility: Checks - if not info.is_pycall_built(): # pragma: no cover - return False - if not info.is_compatible_python(): # pragma: no cover - return False - - # Confirm Julia() is callable - try: - Julia() - return True - except JuliaError: # pragma: no cover - return False - - def have_jax(): """Check if jax and jaxlib are installed with the correct versions""" return ( diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 5415ecb0e2..9f2f417533 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -10,7 +10,7 @@ from platform import system from collections import OrderedDict -have_julia = pybamm.have_julia() +have_julia = true if have_julia and system() != "Windows": from juliacall import Main diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index 53b3ea8117..8c9c153f3e 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -92,17 +92,6 @@ def test_git_commit_info(self): self.assertIsInstance(git_commit_info, str) self.assertEqual(git_commit_info[:2], "v2") - @unittest.skipIf(not pybamm.have_julia(), "Julia not installed") - def test_have_julia(self): - # Remove julia from the path - with unittest.mock.patch.dict( - "os.environ", {"PATH": os.path.dirname(sys.executable)} - ): - self.assertFalse(pybamm.have_julia()) - - # Add it back - self.assertTrue(pybamm.have_julia()) - class TestSearch(unittest.TestCase): def test_url_gets_to_stdout(self): From 957b39b88241c229be901ce316c9e6698e5891bb Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 15:53:22 -0400 Subject: [PATCH 147/163] have julia --- pybamm/__init__.py | 1 - .../operations/evaluate_julia.py | 163 +++++++++++------- 2 files changed, 96 insertions(+), 68 deletions(-) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 008d0485c9..4ebd107dbe 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -49,7 +49,6 @@ have_jax, install_jax, is_jax_compatible, - have_julia, get_git_commit_info, ) from .logger import logger, set_logging_level diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 4869bb7e60..24f867fda7 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -8,12 +8,12 @@ from math import floor import graphlib + class FunctionRepeat(object): def __init__(self, expr, inputs): pass - pass - + pass class PsuedoInputParameter(pybamm.InputParameter): @@ -23,15 +23,15 @@ def create_copy(self): self.name, self.domain, expected_size=self._expected_size ) return new_input_parameter - + @property def children(self): return self._children - + @children.setter def children(self, expr): self._children = expr - + def set_psuedo(symbol, expr): if isinstance(symbol, PsuedoInputParameter): @@ -50,22 +50,21 @@ def remove_lines_with(input_string, pattern): my_string = my_string + s + "\n" return my_string -#Wrapper to designate a function. -#Bottom needs to already have a julia -#conversion. (for shape.) + +# Wrapper to designate a function. +# Bottom needs to already have a julia +# conversion. (for shape.) class PybammJuliaFunction(pybamm.Symbol): def __init__(self, children, expr, name): self.expr = expr super().__init__(name, children) + def evaluate_for_shape(self): return self.expr.evaluate_for_shape() - + @property def shape(self): return self.expr.shape - - - class JuliaConverter(object): @@ -79,13 +78,13 @@ def __init__( input_parameter_order=None, inline=True, parallel="legacy-serial", - outputs = [], - inputs = [], - black_box = False + outputs=[], + inputs=[], + black_box=False, ): - #if len(outputs) != 1: + # if len(outputs) != 1: # raise NotImplementedError("Julia black box can only have 1 output") - + if ismtk: raise NotImplementedError("mtk is not supported") @@ -209,10 +208,7 @@ def _convert_tree_to_intermediate(self, symbol): shape = symbol.shape name = symbol.name self._intermediate[my_id] = JuliaJuliaFunction( - child_ids, - my_id, - shape, - name + child_ids, my_id, shape, name ) elif isinstance(symbol, pybamm.MatrixMultiplication): # Break down the binary tree @@ -415,7 +411,7 @@ def create_cache(self, symbol, cache_name=None): self._cache_id += 1 if cache_name is None: cache_name = "cache_{}".format(cache_id) - + if self._preallocate: if self._cache_type == "standard": if cache_shape[1] == 1: @@ -554,46 +550,46 @@ def write_function(self): def write_black_box(self, funcname): top = self._intermediate[next(reversed(self._intermediate))] if len(self.outputs) != 1: - raise NotImplementedError( - "only 1 output is allowed!" - ) + raise NotImplementedError("only 1 output is allowed!") if not self._preallocate: - raise NotImplementedError( - "black box only supports preallocation." - ) - #this will automatically be in place with the correct function name - top_var_name = top._convert_intermediate_to_code(self, inline=False, cache_name=self.outputs[0]) - - #still need to write the function. + raise NotImplementedError("black box only supports preallocation.") + # this will automatically be in place with the correct function name + top_var_name = top._convert_intermediate_to_code( + self, inline=False, cache_name=self.outputs[0] + ) + + # still need to write the function. self.write_function() self._cache_and_const_string = ( "begin\n{} = let \n".format(funcname) + self._cache_and_const_string ) - - #No need to write a cache since it's in the input. + + # No need to write a cache since it's in the input. self._cache_and_const_string = remove_lines_with( self._cache_and_const_string, top_var_name ) - #may need to modify this logic a bit in the future. + # may need to modify this logic a bit in the future. if "p" in self.inputs: parameter_string = "" for parameter in self.input_parameter_order: parameter_string += "{},".format(parameter) parameter_string += "= p\n" self._function_string = parameter_string + self._function_string - - #same as _easy + + # same as _easy self._function_string = ( self._cache_initialization_string + self._function_string ) - #no support for not preallocating. (for now) + # no support for not preallocating. (for now) self._function_string += "\n return nothing\nend\nend\nend" - header_string = "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name + header_string = ( + "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name + ) for this_input in self.inputs: header_string = header_string + "," + this_input - header_string +=")\n" + header_string += ")\n" self._function_string = header_string + self._function_string return 0 @@ -658,14 +654,14 @@ def write_function_easy(self, funcname, inline=True): # this function will be the top level. def convert_tree_to_intermediate(self, symbol, len_rhs=None): if isinstance(symbol, pybamm.PybammJuliaFunction): - #need to hash this out a bit more. + # need to hash this out a bit more. self._black_box = True self.outputs = ["out"] self.inputs = [] self.funcname = symbol.name - #process inputs: input types can be StateVectors, - #StateVectorDots, parameters, time, and psuedo - # parameters. + # process inputs: input types can be StateVectors, + # StateVectorDots, parameters, time, and psuedo + # parameters. elif self._dae_type == "implicit": symbol_minus_dy = [] end = 0 @@ -696,34 +692,39 @@ def build_julia_code(self, funcname="f", inline=True): string = self._cache_and_const_string + self._function_string return string -#this is a bit of a weird one, may change it at some point + +# this is a bit of a weird one, may change it at some point class JuliaJuliaFunction(object): def __init__(self, inputs, output, shape, name): self.inputs = inputs self.output = output self.shape = shape self.name = name - - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False, cache_name=None): + + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=False, cache_name=None + ): if converter.cache_exists(self.output, self.inputs): return converter._cache_dict[self.output] - result_var_name = converter.create_cache(self, cache_name = cache_name) + result_var_name = converter.create_cache(self, cache_name=cache_name) input_var_names = [] for input in self.inputs: input_var_names.append( - converter._intermediate[input]._convert_intermediate_to_code(converter, inline=inline) + converter._intermediate[input]._convert_intermediate_to_code( + converter, inline=inline + ) ) result_var_name = converter._cache_dict[self.output] code = "{}({}".format(self.name, result_var_name) for input in input_var_names: - code = code+","+input - code = code+")\n" + code = code + "," + input + code = code + ")\n" - #black box always generates a cache. + # black box always generates a cache. self.generate_code_and_dag(converter, code) return result_var_name - + def generate_code_and_dag(self, converter, code): converter._code[self.output] = code converter._dag[self.output] = set(self.inputs) @@ -765,10 +766,12 @@ def __init__(self, left_input, right_input, output, shape): self.output = output self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=False, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=False, cache_name=None + ): if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] - result_var_name = converter.create_cache(self, cache_name = cache_name) + result_var_name = converter.create_cache(self, cache_name=cache_name) left_input_var_name, right_input_var_name = self.get_binary_inputs( converter, inline=False ) @@ -795,7 +798,9 @@ def __init__(self, left_input, right_input, output, shape, operator): self.shape = shape self.operator = operator - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] inline = inline & converter._inline @@ -860,7 +865,9 @@ def __init__(self, left_input, right_input, output, shape, name): self.shape = shape self.name = name - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, [self.left_input, self.right_input]): return converter._cache_dict[self.output] inline = inline & converter._inline @@ -915,7 +922,9 @@ def generate_code_and_dag(self, converter: JuliaConverter, code): if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] inline = inline & converter._inline @@ -943,7 +952,9 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, class JuliaNegation(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] inline = inline & converter._inline @@ -967,7 +978,9 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, class JuliaMinimumMaximum(JuliaBroadcastableFunction): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] result_var_name = converter.create_cache(self, cache_name=cache_name) @@ -997,7 +1010,9 @@ def generate_code_and_dag(self, converter: JuliaConverter, code): if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, [self.input]): return converter._cache_dict[self.output] index = self.index @@ -1088,7 +1103,9 @@ def __init__(self, my_id, loc, shape): self.loc = loc self.shape = shape - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): start = self.loc[0] + 1 end = self.loc[1] self.generate_code_and_dag(converter) @@ -1099,7 +1116,9 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, class JuliaStateVectorDot(JuliaStateVector): - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): start = self.loc[0] + 1 end = self.loc[1] self.generate_code_and_dag(converter) @@ -1115,7 +1134,9 @@ def __init__(self, my_id, value): self.value = float(value) self.shape = (1, 1) - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): self.generate_code_and_dag(converter) return self.value @@ -1155,7 +1176,9 @@ def generate_code_and_dag(self, converter: JuliaConverter, code): if converter._parallel == "legacy-serial": converter._function_string += code - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, self.children): return converter._cache_dict[self.output] num_cols = self.shape[1] @@ -1215,7 +1238,11 @@ def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, if converter._preallocate: if vec: code += "@. {}[{}:{}{} = {}\n".format( - my_name, start_row, start_row, right_parenthesis, child_var_name + my_name, + start_row, + start_row, + right_parenthesis, + child_var_name, ) else: code += "@. {}[{}{} = {} \n".format( @@ -1259,7 +1286,9 @@ def __init__( self.secondary_dimension_npts = secondary_dimension_npts self.children_slices = children_slices - def _convert_intermediate_to_code(self, converter: JuliaConverter, inline=True, cache_name=None): + def _convert_intermediate_to_code( + self, converter: JuliaConverter, inline=True, cache_name=None + ): if converter.cache_exists(self.output, self.children): return converter._cache_dict[self.output] num_cols = self.shape[1] From 04308571e6816c3246fdea4c799770cdb2368738 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 15:54:11 -0400 Subject: [PATCH 148/163] rm pack --- .../expression_tree/operations/build_pack.py | 388 ------------------ 1 file changed, 388 deletions(-) delete mode 100644 pybamm/expression_tree/operations/build_pack.py diff --git a/pybamm/expression_tree/operations/build_pack.py b/pybamm/expression_tree/operations/build_pack.py deleted file mode 100644 index eb1426a007..0000000000 --- a/pybamm/expression_tree/operations/build_pack.py +++ /dev/null @@ -1,388 +0,0 @@ -# -# convert an expression tree into a pack model -# - -#TODO -# - Test sign convention -# - Eliminate node1x & node1y (use graph only) -# - Thermals -# - FunctionPack -import pybamm -from copy import deepcopy -import networkx as nx -import numpy as np -import pandas as pd -import liionpack as lp - -class offsetter(object): - def __init__(self, offset): - self._sv_done = [] - self.offset = offset - - - def add_offset_to_state_vectors(self, symbol): - # this function adds an offset to the state vectors - new_y_slices = () - if isinstance(symbol, pybamm.StateVector): - # need to make sure its in place - if symbol.id not in self._sv_done: - for this_slice in symbol.y_slices: - start = this_slice.start + self.offset - stop = this_slice.stop + self.offset - step = this_slice.step - new_slice = slice(start, stop, step) - new_y_slices += (new_slice,) - symbol.replace_y_slices(*new_y_slices) - symbol.set_id() - self._sv_done += [symbol.id] - - elif isinstance(symbol, pybamm.StateVectorDot): - raise NotImplementedError("Idk what this means") - else: - for child in symbol.children: - self.add_offset_to_state_vectors(child) - child.set_id() - symbol.set_id() - - - -class PsuedoInputParameter(pybamm.InputParameter): - def create_copy(self): - """See :meth:`pybamm.Symbol.new_copy()`.""" - new_input_parameter = PsuedoInputParameter( - self.name, self.domain, expected_size=self._expected_size - ) - return new_input_parameter - - @property - def children(self): - return self._children - - @children.setter - def children(self, expr): - self._children = expr - - -def set_psuedo(symbol, expr): - if isinstance(symbol, PsuedoInputParameter): - symbol.children = [expr] - else: - for child in symbol.children: - set_psuedo(child, expr) - symbol.set_id() - - -class Pack(object): - def __init__(self, model, netlist, parameter_values=None): - # this is going to be a work in progress for a while: - # for now, will just do it at the julia level - - # Build the cell expression tree with necessary parameters. - # think about moving this to a separate function. - - if parameter_values is not None: - raise AssertionError("parameter values not supported") - parameter_values = model.default_parameter_values - parameter_values.update( - {"Current function [A]": pybamm.PsuedoInputParameter("cell_current")} - ) - self.cell_parameter_values = parameter_values -# - sim = pybamm.Simulation(model, parameter_values=parameter_values) - sim.build() - self.cell_model = pybamm.numpy_concatenation( - sim.built_model.concatenated_rhs, sim.built_model.concatenated_algebraic - ) - - self.timescale = sim.built_model.timescale - - self.len_cell_rhs = sim.built_model.len_rhs - self.len_cell_algebraic = sim.built_model.len_alg - - self.cell_size = self.cell_model.shape[0] - self._sv_done = [] - self.built_model = sim.built_model - - self.netlist = netlist - self.process_netlist() - - # get x and y coords for nodes from graph. - node_xs = [n for n in range(max(self.circuit_graph.nodes) + 1)] - node_ys = [n for n in range(max(self.circuit_graph.nodes) + 1)] - for row in netlist.itertuples(): - node_xs[row.node1] = row.node1_x - node_ys[row.node1] = row.node1_y - self.node_xs = node_xs - self.node_ys = node_ys - self.batt_string = None - - def lolz(self, Ns, Np): - if self.batt_string is None: - one_parallel = "" - for np in range(Np): - one_parallel += "🔋" - batt = "" - for ns in range(Ns): - batt += one_parallel + "\n" - self.batt_string = batt - print(self.batt_string) - - def process_netlist(self): - curr = [{} for i in range(len(self.netlist))] - self.netlist.insert(0, "currents", curr) - - self.netlist = self.netlist.rename( - columns={"node1": "source", "node2": "target"} - ) - self.netlist["positive_node"] = self.netlist["source"] - self.netlist["negative_node"] = self.netlist["target"] - self.circuit_graph = nx.from_pandas_edgelist(self.netlist, edge_attr=True) - - #Function that adds new cells, and puts them in the appropriate places. - def add_new_cell(self): - # TODO: deal with variables dict here too. - # This is the place to get clever. - new_model = deepcopy(self.cell_model) - # at some point need to figure out parameters - my_offsetter = offsetter(self.offset) - my_offsetter.add_offset_to_state_vectors(new_model) - new_model.set_id() - return new_model - - def get_new_terminal_voltage(self): - symbol = deepcopy(self.built_model.variables["Terminal voltage [V]"]) - my_offsetter = offsetter(self.offset) - my_offsetter.add_offset_to_state_vectors(symbol) - return symbol - - def build_pack(self): - # this function builds expression trees to compute the current. - - # cycle basis is the list of loops over which we will do kirchoff mesh analysis - mcb = nx.minimum_cycle_basis(self.circuit_graph) - - # generate loop currents and current source voltages-- this is what we don't know. - num_loops = len(mcb) - - curr_sources = [edge for edge in self.circuit_graph.edges if self.circuit_graph.edges[edge]["desc"][0]=="I"] - - loop_currents = [ - pybamm.StateVector(slice(n,n+1), name="current_{}".format(n)) - for n in range(num_loops) - ] - #print(loop_current.y_slices for loop_current in loop_currents) - - curr_sources = [] - n = num_loops - for edge in self.circuit_graph.edges: - if self.circuit_graph.edges[edge]["desc"][0] == "I": - self.circuit_graph.edges[edge]["voltage"] = pybamm.StateVector(slice(n, n + 1), name="current_source_{}".format(n)) - n += 1 - curr_sources.append(edge) - - # now we know the offset, we should "build" the batteries here. will still need to replace the currents later. - self.offset = num_loops + len(curr_sources) - self.batteries = {} - cells = [] - for desc in self.netlist.desc: - if desc[0] == "V": - new_cell = self.add_new_cell() - cells.append(new_cell) - terminal_voltage = self.get_new_terminal_voltage() - self.batteries.update({desc: {"cell" : new_cell, "voltage" : terminal_voltage, "current_replaced" : False}}) - self.offset += self.cell_size - self.num_cells = len(cells) - cell_eqs = pybamm.numpy_concatenation(*cells) - - if len(curr_sources) != 1: - raise NotImplementedError("can't do this yet") - # copy the basis which we can use to place the loop currents - basis_to_place = deepcopy(mcb) - self.place_currents(loop_currents, basis_to_place) - pack_eqs = self.build_pack_equations(loop_currents, curr_sources) - self.len_pack_eqs = len(pack_eqs) - pack_eqs = pybamm.numpy_concatenation(*pack_eqs) - - - self.pack = pybamm.numpy_concatenation(pack_eqs, cell_eqs) - self.ics = self.initialize_pack(num_loops, len(curr_sources)) - - - def initialize_pack(self, num_loops, num_curr_sources): - curr_ics = pybamm.Vector([1.0 for curr_source in range(num_loops)]) - curr_source_v_ics = pybamm.Vector([1.0 for curr_source in range(num_curr_sources)]) - cell_ics = pybamm.numpy_concatenation(*[self.built_model.concatenated_initial_conditions for n in range(len(self.batteries))]) - ics = pybamm.numpy_concatenation(*[curr_ics, curr_source_v_ics, cell_ics]) - return ics - - - def build_pack_equations(self, loop_currents, curr_sources): - # start by looping through the loop currents. Sum Voltages - pack_equations = [] - cells = [] - for i, loop_current in enumerate(loop_currents): - # loop through the edges - eq = [] - for edge in self.circuit_graph.edges: - if loop_current in self.circuit_graph.edges[edge]["currents"]: - #get the name of the edge current. - edge_type = self.circuit_graph.edges[edge]["desc"][0] - desc = self.circuit_graph.edges[edge]["desc"] - direction = self.circuit_graph.edges[edge]["currents"][loop_current] - this_edge_current = loop_current - for current in self.circuit_graph.edges[edge]["currents"]: - if current == loop_current: - continue - if self.circuit_graph.edges[edge]["currents"][current] == "positive": - this_edge_current = this_edge_current + current - else: - this_edge_current = this_edge_current - current - if edge_type == "R": - eq.append(this_edge_current * self.circuit_graph.edges[edge]["value"]) - elif edge_type == "I": - curr_source_num = self.circuit_graph.edges[edge]["desc"][1:] - if curr_source_num != "0": - raise NotImplementedError( - "multiple current sources is not yet supported" - ) - - if direction == "positive": - eq.append(self.circuit_graph.edges[edge]["voltage"]) - else: - eq.append(-self.circuit_graph.edges[edge]["voltage"]) - elif edge_type == "V": - #first, check and see if the battery has been done yet. - if not self.batteries[self.circuit_graph.edges[edge]["desc"]]["current_replaced"]: - currents = list(self.circuit_graph.edges[edge]["currents"]) - - if self.circuit_graph.edges[edge]["currents"][currents[0]] == "positive": - expr = currents[0] - else: - expr = -currents[0] - for current in currents[1:]: - if self.circuit_graph.edges[edge]["currents"][current] == "positive": - expr = expr+current - else: - expr = expr-current - set_psuedo(self.batteries[self.circuit_graph.edges[edge]["desc"]]["cell"], expr) - voltage = self.batteries[self.circuit_graph.edges[edge]["desc"]]["voltage"] - if direction =="positive": - eq.append(voltage) - else: - eq.append(-voltage) - #check to see if the battery input current has been replaced yet. - #If not, replace the current with the actual current. - - - if len(eq) == 0: - raise NotImplementedError( - "packs must include at least 1 circuit element" - ) - elif len(eq) == 1: - expr = eq[0] - else: - expr = eq[0] + eq[1] - for e in range(2, len(eq)): - expr = expr + eq[e] - # add equation to the pack. - pack_equations.append(expr) - - # then loop through the current source voltages. Sum Currents. - for i,curr_source in enumerate(curr_sources): - currents = list(self.circuit_graph.edges[curr_source]["currents"]) - expr = pybamm.Scalar(self.circuit_graph.edges[curr_source]["value"]) - for current in currents: - if self.circuit_graph.edges[curr_source]["currents"][current] == "positive": - expr = expr+current - else: - expr = expr-current - pack_equations.append(expr) - - #concatenate all the pack equations and return it. - return pack_equations - - - - - # This function places the currents on the edges in a predefined order. - # it begins at loop 0, and loops through each "loop" -- really a cycle - # of the mcb (minimum cycle basis) of the graph which defines the circuit. - # Once it finds a loop in which the current node is in, it places the - # loop current on each edge. Once the loop is finished, it removes the - # loop and then proceeds to the next node and does the same thing. It - # loops until all the loop currents have been placed. - def place_currents(self, loop_currents, mcb): - bottom_loop = 0 - for this_loop, loop in enumerate(mcb): - for node in sorted(self.circuit_graph.nodes): - if node in loop: - # setting var to remove the loop later - done_nodes = set() - # doesn't actually matter where we start. - # loop will always be a set. - if len(loop) != len(set(loop)): - raise NotImplementedError() - inner_node = node - # calculate the centroid of the loop - loop_xs = [self.node_xs[n] for n in loop] - loop_ys = [self.node_ys[n] for n in loop] - centroid_x = np.mean(loop_xs) - centroid_y = np.mean(loop_ys) - last_one = False - while True: - done_nodes.add(inner_node) - - my_neighbors = set( - self.circuit_graph.neighbors(inner_node) - ).intersection(set(loop)) - - # if there are no neighbors in the group that have not been done, ur done! - my_neighbors = my_neighbors - done_nodes - - if len(my_neighbors) == 0: - break - elif len(loop) == len(done_nodes) + 1 and not last_one: - last_one = True - done_nodes.remove(node) - - # calculate the angle to all the neighbors. - # then, to go clockwise, pick the one with - # the largest angle. - my_x = self.node_xs[inner_node] - my_y = self.node_ys[inner_node] - angles = { - n: np.arctan2( - self.node_xs[n] - centroid_x, - self.node_ys[n] - centroid_y, - ) - for n in my_neighbors - } - next_node = max(angles, key=angles.get) - # print("at node {}, now going to node {}".format(inner_node, next_node)) - # print(len(angles)) - - # now, define the vector from the current node to the next node. - next_coords = [ - self.node_xs[next_node] - my_x, - self.node_ys[next_node] - my_y, - ] - - # go find the edge. - - edge = self.circuit_graph.edges.get((inner_node, next_node)) - if edge is None: - edge = self.circuit_graph.edges.get((next_node, inner_node)) - if edge is None: - raise KeyError("uh oh") - - # add this current to the loop. - if inner_node == edge["positive_node"]: - direction = "negative" - else: - direction = "positive" - - edge["currents"].update( - {loop_currents[this_loop]: direction} - ) - inner_node = next_node - break From 8cfef003a71fea026469f9c54267c8d14d968e92 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 15:55:16 -0400 Subject: [PATCH 149/163] rm pack --- pybamm/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 4ebd107dbe..29f03a2d29 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -100,7 +100,6 @@ JuliaConverter, PybammJuliaFunction ) -from .expression_tree.operations.build_pack import Pack, PsuedoInputParameter # # Model classes From 172454567a85ccede636400a3216451d0bb72ffa Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 15:56:19 -0400 Subject: [PATCH 150/163] flake8 --- .../test_expression_tree/test_operations/test_evaluate_julia.py | 2 +- tests/unit/test_models/test_base_model_generate_julia_diffeq.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index 9f2f417533..b8f40bb422 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -10,7 +10,7 @@ from platform import system from collections import OrderedDict -have_julia = true +have_julia = True if have_julia and system() != "Windows": from juliacall import Main diff --git a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py index fea95b338c..b87f6081b5 100644 --- a/tests/unit/test_models/test_base_model_generate_julia_diffeq.py +++ b/tests/unit/test_models/test_base_model_generate_julia_diffeq.py @@ -5,7 +5,7 @@ import unittest import pybamm -have_julia = pybamm.have_julia() +have_julia = True if have_julia and platform.system() != "Windows": from juliacall import Main From 216136d98585d905eb4ff11add8171102d2465a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Oct 2022 20:00:58 +0000 Subject: [PATCH 151/163] style: pre-commit fixes --- pybamm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 29f03a2d29..456da8c546 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -98,7 +98,7 @@ from .expression_tree.operations.replace_symbols import SymbolReplacer from .expression_tree.operations.evaluate_julia import ( JuliaConverter, - PybammJuliaFunction + PybammJuliaFunction, ) # From d7881094398032e1204814a71b392d0398dce2e7 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 16:14:38 -0400 Subject: [PATCH 152/163] fix psuedo and tox --- pybamm/__init__.py | 3 ++- tox.ini | 10 ---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 29f03a2d29..c6b8e794f3 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -98,7 +98,8 @@ from .expression_tree.operations.replace_symbols import SymbolReplacer from .expression_tree.operations.evaluate_julia import ( JuliaConverter, - PybammJuliaFunction + PybammJuliaFunction, + PsuedoInputParameter ) # diff --git a/tox.ini b/tox.ini index 57bd58fc6d..1e6e8d9dfb 100644 --- a/tox.ini +++ b/tox.ini @@ -27,16 +27,6 @@ commands = dev-!windows-!mac: sh -c "echo export LD_LIBRARY_PATH={env:LD_LIBRARY_PATH} >> {envbindir}/activate" doctests: python run-tests.py --doctest -[testenv:julia] -platform = [linux, darwin] -skip_install = true -passenv = HOME -whitelist_externals = git -deps = - julia -commands = - python -c "import julia; julia.install()" - [testenv:pybamm-requires] platform = [linux, darwin] skip_install = true From d37e54d7529a553221b934b0393ecbb615c3f4f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Oct 2022 20:16:28 +0000 Subject: [PATCH 153/163] style: pre-commit fixes --- pybamm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index c6b8e794f3..836904ac9d 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -99,7 +99,7 @@ from .expression_tree.operations.evaluate_julia import ( JuliaConverter, PybammJuliaFunction, - PsuedoInputParameter + PsuedoInputParameter, ) # From 7fd1ae7e32ee3447bfc23f46932d2cbe3edc3b3f Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 16:22:01 -0400 Subject: [PATCH 154/163] remove tox install julia --- .github/workflows/test_on_push.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test_on_push.yml b/.github/workflows/test_on_push.yml index 203af0e771..522864f738 100644 --- a/.github/workflows/test_on_push.yml +++ b/.github/workflows/test_on_push.yml @@ -92,10 +92,6 @@ jobs: if: matrix.os == 'ubuntu-latest' run: tox -e pybamm-requires - - name: Install Julia - if: matrix.os != 'windows-latest' - run: tox -e julia - - name: Run unit tests for GNU/Linux with Python 3.8 if: matrix.os == 'ubuntu-latest' && matrix.python-version != 3.9 run: python -m tox -e unit From 09886591fab63eb18bfab815ba7ab7ff6d351dfe Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 16:38:54 -0400 Subject: [PATCH 155/163] codacy --- .../operations/evaluate_julia.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 24f867fda7..206f4b5d2d 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -8,14 +8,6 @@ from math import floor import graphlib - -class FunctionRepeat(object): - def __init__(self, expr, inputs): - pass - - pass - - class PsuedoInputParameter(pybamm.InputParameter): def create_copy(self): """See :meth:`pybamm.Symbol.new_copy()`.""" @@ -78,9 +70,7 @@ def __init__( input_parameter_order=None, inline=True, parallel="legacy-serial", - outputs=[], - inputs=[], - black_box=False, + black_box=False ): # if len(outputs) != 1: # raise NotImplementedError("Julia black box can only have 1 output") @@ -709,7 +699,7 @@ def _convert_intermediate_to_code( result_var_name = converter.create_cache(self, cache_name=cache_name) input_var_names = [] - for input in self.inputs: + for this_input in self.inputs: input_var_names.append( converter._intermediate[input]._convert_intermediate_to_code( converter, inline=inline @@ -717,8 +707,8 @@ def _convert_intermediate_to_code( ) result_var_name = converter._cache_dict[self.output] code = "{}({}".format(self.name, result_var_name) - for input in input_var_names: - code = code + "," + input + for this_input in input_var_names: + code = code + "," + this_input code = code + ")\n" # black box always generates a cache. From befbe5a3b6b53661917eb46b1c961dfcfb6b52bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Oct 2022 20:40:23 +0000 Subject: [PATCH 156/163] style: pre-commit fixes --- pybamm/expression_tree/operations/evaluate_julia.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 206f4b5d2d..df5e91455c 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -8,6 +8,7 @@ from math import floor import graphlib + class PsuedoInputParameter(pybamm.InputParameter): def create_copy(self): """See :meth:`pybamm.Symbol.new_copy()`.""" @@ -70,7 +71,7 @@ def __init__( input_parameter_order=None, inline=True, parallel="legacy-serial", - black_box=False + black_box=False, ): # if len(outputs) != 1: # raise NotImplementedError("Julia black box can only have 1 output") From e6dc191498ccf83c47527791596dddd0e2e34db9 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 16:52:14 -0400 Subject: [PATCH 157/163] remove pack test --- .../test_operations/test_evaluate_julia.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py index b8f40bb422..d6da289e3d 100644 --- a/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py +++ b/tests/unit/test_expression_tree/test_operations/test_evaluate_julia.py @@ -103,15 +103,6 @@ def test_converter_julia(self): converter.clear() self.assertEqual(converter._intermediate, OrderedDict()) - def test_pack(self): - a = pybamm.StateVector(slice(0, 2)) - A = pybamm.Matrix(np.random.rand(2, 2)) - - expr = A @ a - y_test = np.random.rand(6) - pack = pybamm.Pack(expr, 2) - self.evaluate_and_test_equal(pack.built_model, y_test, decimal=1e-8) - def test_evaluator_julia(self): a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) From fe2cfcfef1f3276acba97a12f2a306f1b7aa7762 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Thu, 27 Oct 2022 21:07:02 -0400 Subject: [PATCH 158/163] fix another havejulia error --- docs/source/util.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/source/util.rst b/docs/source/util.rst index f6074eacf4..937771d0ad 100644 --- a/docs/source/util.rst +++ b/docs/source/util.rst @@ -22,8 +22,6 @@ Utility functions .. autofunction:: pybamm.get_parameters_filepath -.. autofunction:: pybamm.have_julia - .. autofunction:: pybamm.have_jax .. autofunction:: pybamm.is_jax_compatible From 67f423f7a01aa9708ee3bafc005621a23684282a Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Fri, 28 Oct 2022 10:07:40 -0400 Subject: [PATCH 159/163] trying to fix mac --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1e6e8d9dfb..974efc5a14 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {windows}-{tests,unit,dev},tests,unit,dev,julia +envlist = {windows}-{tests,unit,dev},tests,unit,dev [testenv] skipsdist = true From 9b2610b7ba6748e4410608994f6fb1bfcf09259c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Fri, 28 Oct 2022 11:57:18 -0400 Subject: [PATCH 160/163] update black box params --- .../operations/evaluate_julia.py | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index df5e91455c..c03bfebfb6 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -71,6 +71,7 @@ def __init__( input_parameter_order=None, inline=True, parallel="legacy-serial", + inplace = True, black_box=False, ): # if len(outputs) != 1: @@ -92,6 +93,7 @@ def __init__( self._ismtk = ismtk self._jacobian_type = jacobian_type self._preallocate = preallocate + self._inplace = inplace self._dae_type = dae_type self._type = "Float64" @@ -125,6 +127,8 @@ def __init__( self._function_string = "" self._return_string = "" self._cache_initialization_string = "" + self.inputs = [] + self.outputs = [] def cache_exists(self, my_id, inputs): existance = self._cache_dict.get(my_id) is not None @@ -333,12 +337,19 @@ def _convert_tree_to_intermediate(self, symbol): elif isinstance(symbol, pybamm.Time): my_id = symbol.id self._intermediate[my_id] = JuliaTime(my_id) + if self._black_box and "t" not in self.inputs: + self.inputs.append("t") elif isinstance(symbol, pybamm.PsuedoInputParameter): - my_id = self._convert_tree_to_intermediate(symbol.children[0]) + if self._black_box and symbol.name not in self.inputs: + self.inputs.append(symbol.name) + else: + my_id = self._convert_tree_to_intermediate(symbol.children[0]) elif isinstance(symbol, pybamm.InputParameter): my_id = symbol.id name = symbol.name self._intermediate[my_id] = JuliaInput(my_id, name) + if self._black_box and "p" not in self.inputs: + self.inputs.append("p") elif isinstance(symbol, pybamm.StateVector): my_id = symbol.id first_point = symbol.first_point @@ -346,6 +357,8 @@ def _convert_tree_to_intermediate(self, symbol): points = (first_point, last_point) shape = symbol.shape self._intermediate[my_id] = JuliaStateVector(my_id, points, shape) + if self._black_box and "y" not in self.inputs: + self.inputs.append("y") elif isinstance(symbol, pybamm.StateVectorDot): my_id = symbol.id first_point = symbol.first_point @@ -353,6 +366,8 @@ def _convert_tree_to_intermediate(self, symbol): points = (first_point, last_point) shape = symbol.shape self._intermediate[my_id] = JuliaStateVectorDot(my_id, points, shape) + if self._black_box and "dy" not in self.inputs: + self.inputs.append("dy") else: raise NotImplementedError( "Conversion to Julia not implemented for a symbol of type '{}'".format( @@ -542,8 +557,6 @@ def write_black_box(self, funcname): top = self._intermediate[next(reversed(self._intermediate))] if len(self.outputs) != 1: raise NotImplementedError("only 1 output is allowed!") - if not self._preallocate: - raise NotImplementedError("black box only supports preallocation.") # this will automatically be in place with the correct function name top_var_name = top._convert_intermediate_to_code( self, inline=False, cache_name=self.outputs[0] @@ -556,9 +569,10 @@ def write_black_box(self, funcname): ) # No need to write a cache since it's in the input. - self._cache_and_const_string = remove_lines_with( - self._cache_and_const_string, top_var_name - ) + if self._inplace: + self._cache_and_const_string = remove_lines_with( + self._cache_and_const_string, top_var_name + ) # may need to modify this logic a bit in the future. if "p" in self.inputs: @@ -574,10 +588,17 @@ def write_black_box(self, funcname): ) # no support for not preallocating. (for now) - self._function_string += "\n return nothing\nend\nend\nend" - header_string = ( - "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name - ) + if self._inplace: + self._function_string += "\n return nothing\nend\nend\nend" + header_string = ( + "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name + ) + else: + self._function_string += "\n return {}\nend\nend\nend".format(top_var_name) + header_string = ( + "@inbounds function " + funcname + "_with_consts" + "(" + ) + for this_input in self.inputs: header_string = header_string + "," + this_input header_string += ")\n" @@ -613,7 +634,7 @@ def write_function_easy(self, funcname, inline=True): self._function_string = ( self._cache_initialization_string + self._function_string ) - if self._preallocate: + if self._inplace: self._function_string += "\n return nothing\n" else: self._function_string += "\n return {}\n".format(top_var_name) From cf138c01dd9c11e4a32642a9c308480360164594 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:58:06 +0000 Subject: [PATCH 161/163] style: pre-commit fixes --- pybamm/expression_tree/operations/evaluate_julia.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c03bfebfb6..e6d4268217 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -71,7 +71,7 @@ def __init__( input_parameter_order=None, inline=True, parallel="legacy-serial", - inplace = True, + inplace=True, black_box=False, ): # if len(outputs) != 1: @@ -594,11 +594,11 @@ def write_black_box(self, funcname): "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name ) else: - self._function_string += "\n return {}\nend\nend\nend".format(top_var_name) - header_string = ( - "@inbounds function " + funcname + "_with_consts" + "(" + self._function_string += "\n return {}\nend\nend\nend".format( + top_var_name ) - + header_string = "@inbounds function " + funcname + "_with_consts" + "(" + for this_input in self.inputs: header_string = header_string + "," + this_input header_string += ")\n" From 7a0a5d26221236ba05c7867d1dd501150780b22c Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Fri, 28 Oct 2022 13:53:50 -0400 Subject: [PATCH 162/163] finalize julia udf --- .../operations/evaluate_julia.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index c03bfebfb6..de3957376f 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -337,19 +337,17 @@ def _convert_tree_to_intermediate(self, symbol): elif isinstance(symbol, pybamm.Time): my_id = symbol.id self._intermediate[my_id] = JuliaTime(my_id) - if self._black_box and "t" not in self.inputs: - self.inputs.append("t") elif isinstance(symbol, pybamm.PsuedoInputParameter): - if self._black_box and symbol.name not in self.inputs: - self.inputs.append(symbol.name) + if self._black_box: + my_id = symbol.id + name = symbol.name + self._intermediate[my_id] = JuliaInput(my_id, name) else: my_id = self._convert_tree_to_intermediate(symbol.children[0]) elif isinstance(symbol, pybamm.InputParameter): my_id = symbol.id name = symbol.name self._intermediate[my_id] = JuliaInput(my_id, name) - if self._black_box and "p" not in self.inputs: - self.inputs.append("p") elif isinstance(symbol, pybamm.StateVector): my_id = symbol.id first_point = symbol.first_point @@ -357,8 +355,6 @@ def _convert_tree_to_intermediate(self, symbol): points = (first_point, last_point) shape = symbol.shape self._intermediate[my_id] = JuliaStateVector(my_id, points, shape) - if self._black_box and "y" not in self.inputs: - self.inputs.append("y") elif isinstance(symbol, pybamm.StateVectorDot): my_id = symbol.id first_point = symbol.first_point @@ -366,8 +362,6 @@ def _convert_tree_to_intermediate(self, symbol): points = (first_point, last_point) shape = symbol.shape self._intermediate[my_id] = JuliaStateVectorDot(my_id, points, shape) - if self._black_box and "dy" not in self.inputs: - self.inputs.append("dy") else: raise NotImplementedError( "Conversion to Julia not implemented for a symbol of type '{}'".format( @@ -591,7 +585,7 @@ def write_black_box(self, funcname): if self._inplace: self._function_string += "\n return nothing\nend\nend\nend" header_string = ( - "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name + "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name + "," ) else: self._function_string += "\n return {}\nend\nend\nend".format(top_var_name) @@ -600,7 +594,8 @@ def write_black_box(self, funcname): ) for this_input in self.inputs: - header_string = header_string + "," + this_input + header_string = header_string + this_input + "," + header_string = header_string[:-1] header_string += ")\n" self._function_string = header_string + self._function_string return 0 @@ -670,10 +665,21 @@ def convert_tree_to_intermediate(self, symbol, len_rhs=None): self._black_box = True self.outputs = ["out"] self.inputs = [] + for child in symbol.children: + if isinstance(child, pybamm.PsuedoInputParameter) and symbol.name not in self.inputs: + self.inputs.append(child.name) + elif isinstance(child, pybamm.StateVector) and "y" not in self.inputs: + self.inputs.append("y") + elif isinstance(child, pybamm.StateVectorDot) and "dy" not in self.inputs: + self.inputs.append("dy") + elif isinstance(child, pybamm.Time) and "t" not in self.inputs: + self.inputs.append("t") + elif isinstance(child, pybamm.InputParameter) and "p" not in self.inputs: + self.inputs.append("p") self.funcname = symbol.name # process inputs: input types can be StateVectors, # StateVectorDots, parameters, time, and psuedo - # parameters. + # parameters elif self._dae_type == "implicit": symbol_minus_dy = [] end = 0 From b39b8e0125dbc8c70a694ae75c021791e6220e2d Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Fri, 28 Oct 2022 13:54:48 -0400 Subject: [PATCH 163/163] black and yellow --- .../operations/evaluate_julia.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pybamm/expression_tree/operations/evaluate_julia.py b/pybamm/expression_tree/operations/evaluate_julia.py index 5599e00323..0e50b717fe 100644 --- a/pybamm/expression_tree/operations/evaluate_julia.py +++ b/pybamm/expression_tree/operations/evaluate_julia.py @@ -585,7 +585,12 @@ def write_black_box(self, funcname): if self._inplace: self._function_string += "\n return nothing\nend\nend\nend" header_string = ( - "@inbounds function " + funcname + "_with_consts" + "(" + top_var_name + "," + "@inbounds function " + + funcname + + "_with_consts" + + "(" + + top_var_name + + "," ) else: self._function_string += "\n return {}\nend\nend\nend".format( @@ -594,7 +599,7 @@ def write_black_box(self, funcname): header_string = "@inbounds function " + funcname + "_with_consts" + "(" for this_input in self.inputs: - header_string = header_string + this_input + "," + header_string = header_string + this_input + "," header_string = header_string[:-1] header_string += ")\n" self._function_string = header_string + self._function_string @@ -666,15 +671,22 @@ def convert_tree_to_intermediate(self, symbol, len_rhs=None): self.outputs = ["out"] self.inputs = [] for child in symbol.children: - if isinstance(child, pybamm.PsuedoInputParameter) and symbol.name not in self.inputs: + if ( + isinstance(child, pybamm.PsuedoInputParameter) + and symbol.name not in self.inputs + ): self.inputs.append(child.name) elif isinstance(child, pybamm.StateVector) and "y" not in self.inputs: self.inputs.append("y") - elif isinstance(child, pybamm.StateVectorDot) and "dy" not in self.inputs: + elif ( + isinstance(child, pybamm.StateVectorDot) and "dy" not in self.inputs + ): self.inputs.append("dy") elif isinstance(child, pybamm.Time) and "t" not in self.inputs: self.inputs.append("t") - elif isinstance(child, pybamm.InputParameter) and "p" not in self.inputs: + elif ( + isinstance(child, pybamm.InputParameter) and "p" not in self.inputs + ): self.inputs.append("p") self.funcname = symbol.name # process inputs: input types can be StateVectors,