Group Abstract Group Abstract

Message Boards Message Boards

1
|
8.4K Views
|
57 Replies
|
3 Total Likes
View groups...
Share
Share this post:

Referring to arguments of a function by order in which they appear

Posted 1 year ago

I have an application in which I need to pass arguments to a function in non-list form (I know functions can take arguments in list form, but there is a bug that makes this not work in my case, we don't have to get into it), but then I need to recreate the lists from the arguments. So for example, if

f[a1_,a2_]:=a1+a2

I would like to define, inside f a list={a1,a2] and then redefine

f[a1_,a2_]:=list[[1]]+list[[2]]

I would like to refer to a1 as first argument of f, a2 as second argument instead of by their names, because I have a much more complicated function in my real case and I want to automate list by using such Mathematica constructs as Table and Array. So how to do this?

POSTED BY: Iuval Clejan
57 Replies
Posted 1 year ago

I think I solved it! So flattening the lists before passing to ffoutsuper, and reconstructing them should have worked, because that is what I had before, no lists. But I also had the arguments passed to ffoutsuper be _Real. So once I did that, it works again. I don't really know why. Somehow the conditionals work now. It would have been nice not to have to flatten the lists and reconstruct them. Something for the developers to figure out.

POSTED BY: Iuval Clejan

I need to recreate the lists from the arguments

Is this all you really meant? Inside fn, al is a list of the arguments.

fn[args___] := Module[{al=List[args]},
  ...
]

The arguments can also be given their own names:

On[Assert];
fn[args : PatternSequence[a1_, a2_]] := Module[{al = List[args]},
  Assert[a1 == al[[1]]];
  Assert[a2 == al[[2]]];
  Echo["OK"];
  ]
fn[1, 2]

Inside the 2nd function,

I would like to define, inside f, a list={a1,a2} and then redefine f[a1_,a2_]:=list[[1]]+list[[2]]

What do you mean by "redefine"? The best I can tell is that you're asking for a functor (a function-rewriting-function) that takes a function in one form, and returns a function that does the same job but in another form. Please provide concrete examples of a function before and after the modification.

If I were to guess, you want to write, literally, a function like: fn[a1_,a2_]:=a1+a2, pass it to a functor, and get back a function like fn[args:PatternSequence[a1_,a2_]]:=Module[{al=List[args]}, al[[1]]+al[[2]] ].

More concretely, you'd want a rewriter that can be used as follows: rewriter[fn[a1_,a2_]:=a1+a2] returns HoldForm[fn[args:PatternSequance[a1_,a2_]]:=Module[{al=List[args]}, al[[1]]+al[[2]] ] ].

The held form can be then released (using ReleaseHold), and the replacement function will be created. The original function can be Clear-ed in the meantime, i.e. after using the rewriter but before the held form is released.

Please confirm that that is indeed what you'd want. Otherwise, provide a concrete example of what you'd pass into the rewriter, and what result you expect back. Thank you.

there is a bug that makes this not work in my case, we don't have to get into it

That has a strong smell of an XY Problem. Perhaps fixing that "bug" would be desirable instead of devising clumsy workarounds? Could you please give concrete examples of the "bug" you allude to?

I have a much more complicated function in my real case and I want to automate list by using such Mathematica constructs as Table and Array.

There are no fundamental differences between code that generates Part[argList, index] vs code that generates Symbol["arg"<>ToString[index]].

For example, here's a function that creates a function definition for a function that sums a certain number of arguments. The function definition action (SetDelayed) is inactive, so the form of the function can be inspected before the function definition is performed.

makeSum[fn_Symbol, count_Integer] := 
 Module[{argSymbols, argPatterns},
  argSymbols = Table[Symbol["a" <> ToString[i]], {i, count}];
  argPatterns = 
   Activate[ Inactive[Pattern][#, Blank[]] & /@ argSymbols];
  Inactivate[
   fn[Sequence @@ argPatterns] := Total[argSymbols]
   , SetDelayed
   ]
  ]
Clear[f2]
f2[3, 4] (* evaluates to f2[3,4] since f2 was cleared *)
makeSum[f2, 2] (* returns an inactive function definition *)
Echo[% // Activate, "Activating"] (* now the function definition is performed *)
f2[3, 4] (* evaluates to 7, since f2 is now defined: f2[a1_,a2_]:=a1+a2 *)

Mathematica's approach to controlling evaluation in the core language is a bit more clumsy than what you'd get in, say LISP macros. There is macro functionality in Mathematica, but it's not documented, so I didn't use it, even though it would get rid of some of the wrangling with active and inactive heads.

The argument symbols could have been also shielded from evaluating to something else by temporarily making them have no values (using a Block), but I didn't bother with that. That's a TODO.

And of course, what Mathematica version do you use?


A bit of explanation of inactivations folllows. Recall that := is syntactic sugar for SetDelayed. The two lines below are equivalent:

fn[_] := foo
SetDelayed[fn[_], foo]

Thus, we can create an inert function definition that will define the function only once SetDelayed is activated:

Inactivate[ fn[_] := foo, SetDelayed ]
(* equivalent to *)
Inactivate[ SetDelayed[fn[_], foo], SetDelayed ]
(* evaluates to *)
Inactive[SetDelayed][fn[_], foo]

Thus, depending on preference and circumstances, we can either use Inactivate and tell it what heads to deactivate, or use heads wrapped in Inactive directly.

Inactivate[expr_] wraps certain heads inside the expression inside an Inactive head. In Mathematica, expression forms are fully recursive - the head of an expression is usually a symbol, e,g, SetDelayed, but can be an expression itself, e.g. Inactive[SetDelayed]. Example:

Inactivate[5 + 6, Plus]
(* evaluates to *)
Inactive[Plus][5,6]

Then, Activate[%] will reactivate all inactive heads, then the expression will be evaluated, and the addition will be performed, thus Activate[%] here will evaluate to 11.

Posted 1 year ago

Kuba, The problem of delaying the evaluation of ffoutsuper till after eqnlist has been generated has been already solved by Eric. The problem of giving ffoutsuper a flat list and reconstructing the actual lists inside of ffoutsuper (so they can be passed on to other functions) has also been already solved by Eric, so thanks but I don't need that anymore. However, I was wrong to think that this would solve my problem. I thought it would, because when I gave ffoutsuper a flat list and delayed the evaluation of eqnlist with the "Real" hack (imposing that its args be real numbers instead of symbolic or otherwise), everything worked. But now I am thinking that the "Real" hack must have had other effects that made things work (but it doesn't work with lists, at least as far as Derivative is concerned, which is a BUG. See this thread:https://community.wolfram.com/groups/-/m/t/3092236 and Ginalucca's response:" Very curious. It appears that you cannot mix numeric tests like xReal or x?NumberQ with list structures inside Derivative")

So the problem in its most general form is to have the same code that worked without lists, work with lists (of different shapes and sizes).

POSTED BY: Iuval Clejan
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago

Eric, the only thing I still need help with is to index correctly into the lists

ffoutsuper[args__] := fList[{args}];
fList[{flag_, LRc_, cL_, cR_, alphaL_, alphaR_, LRC_, CL_, CR_, 
AlphaL_, AlphaR_}] :=???

flag, LRc and LRC are scalars, cL and cR are 4-vectors, alphaL, alphaR, AlphaL, and AlphaR are 4x4 matrices (for now, the dimensions of lists will change in the future). Is there a good way to do this? The eariler simple example had only one 1-dimensional list, so it was trivial. I need to recreate the original lists from the flattened, joined lists that are served as a sequence to ffoutsuper.

POSTED BY: Iuval Clejan
Posted 1 year ago

If the argument list always has this same structure, you can just refer to the pattern names you've already used:

fList[{flag_, LRc_, cL_, cR_, alphaL_, alphaR_, LRC_, CL_, CR_, AlphaL_, AlphaR_}] := 
 StringForm["``-``-``-``-``-``-``-``-``-``-``", LRc, CR, AlphaL, flag, cL, alphaL, cR, alphaR, CL, AlphaR, LRC];

fList[Range[11]] // ToString
(* "2-9-10-1-3-5-4-6-8-11-7" *)

But if you really want to access by index, then you'll need a pattern name for the whole list:

fList[args : {flag_, LRc_, cL_, cR_, alphaL_, alphaR_, LRC_, CL_, CR_, AlphaL_, AlphaR_}] := 
 StringForm["``-``-``-``-``-``-``-``-``-``-``:::``:::(``)", LRc, CR, AlphaL, flag, cL, alphaL, cR, alphaR, CL, AlphaR, LRC, args, args[[5]]];

fList[Range[11]] // ToString
(* "2-9-10-1-3-5-4-6-8-11-7:::{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}:::(5)" *)

And if you only want to access by index, you don't need all those other pattern names:

fList[args : {_, _, _, _, _, _, _, _, _, _, _}] := args[[RandomSample[Range[11]]]];    

fList[Range[11]]
(* {3, 1, 4, 6, 8, 11, 2, 7, 10, 5, 9} *)

And if you don't actually want to restrict it to 11 arguments, but any arbitrary number:

fList[args : {___}] := RandomSample[args];

fList[Range[5]]
(* {1, 5, 4, 2, 3} *)

And also, you can index into any one particular argument:

fList[{flag_, LRc_, cL_, cR_, alphaL_, alphaR_, LRC_, CL_, CR_, 
   AlphaL_, AlphaR_}] := cL[[2]];

fList[{1, 2, {100, 101, 102, 103}, 4, 5, 6, 7, 8, 9, 10, 11}]
(* 101 *)
POSTED BY: Eric Rimbey
Posted 1 year ago

At least one of us is not understanding the other here. I need to access all these args that come from a flattened list of joined lists for computation. So flag is just flag (a scalar), RLc is just RLc (a scalar), I need to access different element of cL and cR (which are one dimensional lists of length 4), I need to access different elements of aL and aR which are 4x4 2 dimensional lists, etc. I want fList to be the function that has those original lists in it, somehow reconstructed from the flattened sequence that ffoutsuper gets.

How does mixing up the order of the arguments (some of which are lists) and then finding their original order help me?

POSTED BY: Iuval Clejan
Posted 1 year ago

I need to tell flist that the first argument is a scalar called flag, the second also a scalar called RLc, the third through 6th correspond to a 1D list of dimension 4 called cL, etc.

POSTED BY: Iuval Clejan
Posted 1 year ago

I need to tell flist that the first argument is a scalar called flag, the second also a scalar called RLc, the third through 6th correspond to a 1D list of dimension 4 called cL, etc.

If you mean you need to set these conditions as constraints on the arguments, then you could try any of these (I'm going to simplify your example, we don't need eleven arguments to demonstrate this):

myFuncTakesScalars[arg1_Real, arg2_Integer, arg3_?NumericQ, arg4_?AtomQ] := "constraints satisfied";
myFuncTakesScalars[1., 2, Pi, "a"]
(* "constraints satisfied" *)

myFuncTakesLists[arg1 : {_, _, _, _}, arg2_?ArrayQ, arg3_?MatrixQ] := "constraints satisfied";
myFuncTakesLists[{1, 2, 3, 4}, RandomReal[{0, 1}, 100], RandomInteger[{1, 10}, {20, 30}]]
(* "constraints satisfied" *)

myFuncTakesListsWithFurtherConstraints[arg1 : {_, _, _, _}, arg2_?ArrayQ, arg3_?MatrixQ] := "constraints satisfied" /; 4 == Length[arg1] && 2 == ArrayDepth[arg3];
myFuncTakesListsWithFurtherConstraints[{1, 2, 3, 4}, RandomReal[{0, 1}, 100], RandomInteger[{1, 10}, {20, 30}]]
(* "constraints satisfied" *)
POSTED BY: Eric Rimbey
Posted 1 year ago

OK, we're almost there. I don't want constraints. I want to recover the original lists and pass them on to other functions that use them. So I think this does it:

av0 = {1., 1.};
a = Table[av[i][t], {i, 2}]; b = {{0.01, 0.02}};
apat = {x_, y_}; bpat = {{z_, w_}};
vars = a;
varex = Flatten[{a, b}];
varpat = Flatten[{apat, bpat}];
fImplementation[(*x_,y_,z_,w_*)Sequence @@ varpat] := 
  (a = {x, y}; 
   b = {{z, w}}; (1/2) (1 - a[[2]]) a[[1]]^2 + (1 - a[[1]]) a[[2]]^2 +
     a[[1]] a[[2]] - b[[1, 1]] b[[1, 2]]);
eqnlist = {Derivative[1][av[1]][t] == 
   Derivative[1, 0, 0, 0][f] @@ varex, 
  Derivative[1][av[2]][t] == Derivative[0, 1, 0, 0][f] @@ varex, 
  av[1][0] == av0[[1]], av[2][0] == av0[[2]]}
solDEsuper = 
 NDSolve[eqnlist /. f -> fImplementation, vars, {t, 0, 1.4}]

It's a bit unwieldy, but probably good enough. How can I pay you?

POSTED BY: Iuval Clejan
Posted 1 year ago

Ooooooooohhhhhhh. Good grief. I finally understand. It's just so much easier to look at something concrete. Not that it matters now, but maybe you can appreciate my confusion if I explain what I thought you meant. You said

I need to tell flist that the first argument is a scalar called flag, the second also a scalar called RLc,

Now, since you already had arguments named flag and RLc, I thought your problem was telling flist that they were scalars. And so I gave you several ways to apply the constraint that an argument is a scalar. Then you said

the third through 6th correspond to a 1D list of dimension 4 called cL, etc.

which sounded to me like each of these arguments was a list of length 4, not that you wanted to re-interpret the 3rd through 6th arguments as members of a new list. Given your language of "tell flist that..." I wasn't understanding that you were talking about the implementation details instead of the interface. Telling a function about its arguments is what it means to give a function a signature. I don't think I ever, ever would have understood this to mean "flist's implementation should construct a list from arguments 3-6".

Okay, with that out of the way, I'll send some suggestions.

POSTED BY: Eric Rimbey
Posted 1 year ago

The basic problem is that we have two equivalent data structures that have different "shapes". One of the shapes is a flat list and the other has some extra levels of structure. What I don't know is if you have a bunch of different shapes rather than just two. Or maybe you have a bunch of scenarios that all have two shapes, but the two shapes are different across the pairs. So, situation #1 would be having, say 16 arguments that can show up in three different shapes: a flat list, a 4x4 matrix, and a 2x8 matrix. Situation #2 would be that for some particular function, we need to transform between a flat list and a 4x4 matrix, but for some other function we need to transform between a flat list and a 2x8 matrix. Anyway, what I would suggest is that you build helper functions to do the transformations and have your main functions leverage these helpers.

We know we can go from the more structured shape to a flat list with Flatten, so we only need to implement an Unflatten (or maybe several different Unflatten functions, in which case you should name them distinctly). It might also just help readability to be even more explicit about naming the argument patterns.

StructuredPattern = {{_, _}, {{_, _}}};
ListPattern = Flatten[StructuredPattern];

UnflattenSequence[a_, b_, c_, d_] := {{a, b}, {{c, d}}};
UnflattenList[list : ListPattern] := UnflattenSequence @@ list;

FunctionSequence[args___] :=
  <|"what I pass to the list processing function" -> List[args],
   "what I pass to the structure processing function" -> UnflattenSequence[args]|>;
FunctionList[args : ListPattern] :=
  <|"what I pass to the sequence processing function" -> Sequence @@ args,
   "what I pass to the structure processing function" -> UnflattenList[args]|>;
FunctionStructure[args : StructuredPattern] :=
  <|"what I pass to the sequence processing function" -> Sequence @@ Flatten[args],
   "what I pass to the list processing function" -> Flatten[args]|>;

FunctionSequence[w, x, y, z]
(*
  <|"what I pass to the list processing function" -> {w, x, y, z}, 
   "what I pass to the structure processing function" -> {{w, x}, {{y, z}}}|>
 *)

FunctionList[{w, x, y, z}]
(*
  <|"what I pass to the sequence processing function" -> Sequence[w, x, y, z], 
   "what I pass to the structure processing function" -> {{w, x}, {{y, z}}}|>     
 *)

FunctionStructure[{{w, x}, {{y, z}}}]
(*
  <|"what I pass to the sequence processing function" -> Sequence[w, x, y, z], 
   "what I pass to the list processing function" -> {w, x, y, z}|>
 *)
POSTED BY: Eric Rimbey
Posted 1 year ago

Sorry, I am not familiar with associations, but I will learn if you can you try to implement your more general solution to this specific example, so that I can make fflat1 work, instead of having to type in all the dummy elements of all the lists as in fflat2 and then (double tedium) reconstructing them (I commented out all but the first list, because it is too tedious to type in all the dummy arguments, and will only get more tedious as I will have higher dimensional lists in the future)? Somehow, my attempts to make fflat1 receive a sequence with no structure did not work, and it still keeps the structure, as we can see with the failed first attempt to call fflat1 with a structureless sequence, and successful second attempt with a stuctured sequence which contains a list.

POSTED BY: Iuval Clejan
Posted 1 year ago

Sorry, I am not familiar with associations

Sorry for the distraction then. Associations aren't inherent to the problem you're trying to solve. I was just trying to demonstrate how to transform between representations.

I'll try to refactor your code later. I only have a couple of minutes right now. So for now, I'll just try to explain why it failed.

Here's your definition for fflat1 (I'm removing the comments for clarity)

fflat1[Sequence @@ Flatten[{flag_, LRc_, cL_}]] := cL[[1]]

If you look at just the argument list you created, it looks like this.

Sequence @@ Flatten[{flag_, LRc_, cL_}]
(* Sequence[flag_, LRc_, cL_] *)

So, your function expects exactly three arguments. But when you tried to apply it:

fflat1[Sequence @@ Flatten[{1, 0.1, cout0L}]]

you actually provided 6 arguments:

Sequence @@ Flatten[{1, 0.1, cout0L}]
(* Sequence[1, 0.1, 4., 4., 4., 4.] *)

When you applied it the other way:

fflat1[1, 0.1, cout0L]

you did provide exactly three arguments:

Sequence[1, 0.1, cout0L]
(* Sequence[1, 0.1, {4., 4., 4., 4.}] *)

but the last argument was itself a list. So, with the argument constraint satisfied, the function was evaluated to be the first item in the third argument, which is 4..

POSTED BY: Eric Rimbey
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago
fflat2[args__] := (cL = args[[3 ;; 6]]; cL[[1]]^2 ); (*how do I say cL=Table[third through sixth argument of fflat2?*)

Since args is a Sequence, it doesn't do what you expect when used as an argument to Part. The easiest way to fix this is to coerce it to a List:

fflat2[args__] := (cL = {args}[[3 ;; 6]]; cL[[1]]^2 )
POSTED BY: Eric Rimbey
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago

And other issues that are seen in the actual code. Is there a way to tell Derivative to not even try to evaluate something symbolically?

POSTED BY: Iuval Clejan
Posted 1 year ago

If I understood what you're trying to achieve mathematically, maybe I could give you suggestions, but I don't, so I can't. But as a Mathematica programming problem, you might be beating your head against a wall. I'm not sure that we can expect Mathematica to figure out the derivative that you want from the form that you're providing to it.

Not that it really matters to the underlying problem, but you can avoid using temporary variables:

ffoutsuper[args__] := Max[Re[Eigenvalues[Dot @@ ArrayReshape[Drop[{args}, 10], {2, 4, 4}]]]]

I really don't think I can help you if you can't provide simpler examples. I mean, just forget your actual domain completely, and just show an example of dynamically generating a deferred derivative (or whatever we're trying to do here). Show an example where you know the right answer and show me the right answer. The stuff you give me is too far along, and I don't know where to start looking, and so I end up spending a lot of time reverse engineering your code. I'm not willing to do that any longer.

Some general advice: start small and take baby steps. Don't get this far into your coding before you try to fix it. Start with the absolute simplest representation of your problem (or a minimal alternative to your problem) and just do one computation/transformation and make sure it works. Then build on that foundation with one more dead simple computation/transformation. You have so much crazy code here that I don't know if you've made a straight up semantic error or if you're just mis-using a Mathematica function or if we just need to change the order of evaluation. There may be a simple fix, or this whole thing might actually be impossible. I have no idea where we are on that spectrum.

POSTED BY: Eric Rimbey
Posted 1 year ago

I tried your ArrayReshape. It makes no difference either to the timing for Derivative (way too slow) or the bug with the Re'[1]. I am including the same file, but now instead of taking the highest eigenvalue of the product of two of the input matrices, it takes the highest eigenvalue of the product squared. Here it never stops the calculation, even after 20 minutes. There is something very wrong with Derivative, and I want you to admit it, instead of putting it on me to come up with something simpler (are you working for Wolfram?). It might be possible to simplify further the list of args, but why? It is much simpler than my actual application already. Derivative has a problem, maybe it can only take so many variables? Also, please take a look at the two comments in the file.

P.S. I haven't tried the square of the product of matrices with your ArrayReshape, because I don't have the latest version where I am right now, I only have version 8.4 here, which doesn't know about that function. I can try it later today when I get back home to my desktop, but I doubt it will be any different. Maybe Mathematica is not up to this task?

POSTED BY: Iuval Clejan
Posted 1 year ago

I tried your ArrayReshape. It makes no difference either to the timing for Derivative (way too slow) or the bug with the Re'[1].

I'm a bit surprised that it didn't make a difference on timing, but I didn't intend it to fix anything. It's just more wolfram-idiomatic style.

There is something very wrong with Derivative, and I want you to admit it, instead of putting it on me to come up with something simpler (are you working for Wolfram?).

I'm not a Wolfram employee. I don't have to admit anything. Finding the simplest demonstration of something is a tried-and-true debugging technique, but if you don't want to try it, that's fine by me. I'm not invested in any of this--it's your baby.

POSTED BY: Eric Rimbey
Posted 1 year ago

What do you find complicated about my example? Maybe it can't be simplified any further.

POSTED BY: Iuval Clejan
Posted 1 year ago

No need to pay.

POSTED BY: Eric Rimbey
Posted 1 year ago

How does mixing up the order of the arguments (some of which are lists) and then finding their original order help me?

I was just demonstrating various ways of accessing the arguments and parts of argument in the RHS of the function definition. I don't know how you actually need to use the indexes, so I made something up.

Also the title your post here is

Referring to arguments of a function by order in which they appear

so, I dunno, sorry to not be able to read your mind, but it sounds like you're struggling with, I dunno, making the RHS of your function definition refer to the arguments in some way. I'd ask for you to give me a concrete output that you're trying to achieve so that I can then show you how to write your function to produce that output, but I'm afraid you'd insult my intelligence again. Dude, just ask a straightforward question using a minimal example. Provide inputs and expected outputs. If you can't do that, then you'll just have to endure the frustration of watching me trying to figure out what you're asking for.

POSTED BY: Eric Rimbey
Posted 1 year ago

I'm not confident that this is the final answer, but I just want to use this as a point of discussion to try to nail down where the disconnect is. What if we just used a purely formal symbol for the "non-analytical" function and then provided whatever implementation you want as a second function?

av0 = {1., 1.};
varex = Table[av[i][t], {i, 2}];
eqnlist =
  {Derivative[1][av[1]][t] == Derivative[1, 0][f] @@ varex,
   Derivative[1][av[2]][t] == Derivative[0, 1][f] @@ varex,
   av[1][0] == av0[[1]],
   av[2][0] == av0[[2]]}

(* 
  {Derivative[1][av[1]][t] == Derivative[1, 0][f][av[1][t], av[2][t]],
   Derivative[1][av[2]][t] == Derivative[0, 1][f][av[1][t], av[2][t]], 
   av[1][0] == 1., 
   av[2][0] == 1.}
 *)

And then,

fImplementation[x_, y_] := (1/2) (1 - y) x^2 + (1 - x) y^2 + x  y;
eqnlist /. f -> fImplementation
(*
  {Derivative[1][av[1]][t] == av[1][t]*(1 - av[2][t]) + av[2][t] - av[2][t]^2, 
   Derivative[1][av[2]][t] == av[1][t] - av[1][t]^2/2 + 2*(1 - av[1][t])*av[2][t], 
   av[1][0] == 1., 
   av[2][0] == 1.}
 *)

Or even this:

f[x_, y_] := (1/2) (1 - y) x^2 + (1 - x) y^2 + x  y;
av0 = {1., 1.};
varex = Table[av[i][t], {i, 2}];
eqnlist =
 {Derivative[1][av[1]][t] == Derivative[1, 0][Inactive[f]] @@ varex,
  Derivative[1][av[2]][t] == Derivative[0, 1][Inactive[f]] @@ varex,
  av[1][0] == av0[[1]],
  av[2][0] == av0[[2]]}

And then when you want your implementation to be applied:

eqnlist // Activate

Does that get us anywhere?

POSTED BY: Eric Rimbey
Posted 1 year ago

Eric, I'm thinking you might have missed one of my posts, please go back through them all (I should too, to make sure they all posted successfully). I've done my utmost best to simplify the problem and tell you why your solution doesn't work, and tell you exactly what is needed. Your comment sounded sarcastic because if I knew how to "manually change the thing that doesn't work to the thing that does work and show me that" then the problem would be solved.

POSTED BY: Iuval Clejan
Posted 1 year ago

Oh, well then I've completely misunderstood where the problem is.

Look you posted this:

f1[a_] := a[[1]] (1 - a[[2]]) + a[[2]] - a[[2]]^2;
f2[a_] := a[[1]] - 0.5 a[[1]]^2 + 2 (1 - a[[1]]) a[[2]];
av0 = {1., 1.};
varex = Table[av[i][t], {i, 2}];
eqnlist = {Derivative[1][av[1]][t] == f1[varex], 
   Derivative[1][av[2]][t] == f2[varex], av[1][0] == av0[[1]], 
   av[2][0] == av0[[2]]};
solDEsuper = NDSolve[eqnlist, varex, {t, 0, 1.4}]

and said that it worked.

I posted this:

avInit = {1, 1};
avFns = Array[av, 2];
avFnsApplied = Through[avFns[t]]; 

avProtoList[a_List] := 
  0.5  (1 - a[[2]])  a[[1]]^2 + (1 - a[[1]])  a[[2]]^2 + 
   a[[1]]   a[[2]];
avProto[args___] := avProtoList[{args}];

g1 = Derivative[1, 0][avProto];
g2 = Derivative[0, 1][avProto];

eqnlist = {Derivative[1][av[1]][t] == g1 @@ avFnsApplied, 
   Derivative[1][av[2]][t] == g2 @@ avFnsApplied, 
   av[1][0] == avInit[[1]], av[2][0] == avInit[[2]]};
solDEsuper = NDSolve[eqnlist, avFnsApplied, {t, 0, 1.4}]

and you said that it didn't work.

When I look at eqnlist for each of them, I can't tell the difference. I thought if we used a simpler function, I could see the difference by inspection. But you don't want to provide a simpler function. Fine, just tell me what's different about these two. I'm sorry that I'm just not picking up on it, but that's where we are.

But then later, I forced you to tell me whether that expression was correct or not, and you said that it wasn't. So, I thought the problem was in generating eqnlist, and that maybe you could indicate the difference by giving me a reference. But now you seem to be saying that you don't know what eqnlist should even look like, you just know it's wrong. Okay, that's fair, but now we're talking about a mathematics problem, not a programming problem. So, you'll have to lead me through the mathematics before I can provide the code.

Now, you probably think I'm being stupid, and you'll say "no, no, that's not what I said/meant", and that's totally fine. Communication is hard. But I, as of right now, have no idea what/where the problem is. I know that for you, the problem is something about whether arguments are passed as a sequence or as a list. To me, that's a trivial problem, and I'm extremely confident that I can solve it. But I've been failing to produce a good result even though it seems to match the good results you've previously given me. It sounds like you're getting "good answers" with sequences, so I'm just trying to pin down what a good answer is.

So, at the end of the day, it sounds to me like we'd like to feed some inputs into NDSolve. We've got to generate those inputs somehow. You seem to be able to recognize when those inputs are good or not. I don't seem to be able to figure that part out. Also, I'm not really sure where we're starting here. It sounds like there are things "before" we start defining f, f1, etc. I know in your real world problem, those things are big and complicated, but in our toy problem, I would think you could give them to me explicitly. So, my brain is saying, "if he gives me the uber inputs, A, and then the inputs to NDSolve, B, then I can figure out how to transform A into B".

If you can tell me where I'm going wrong in any of the above thinking, that would help greatly. If you can give me any concrete examples that I can compare to at any point in the computation, that would help greatly. If you can try different words to explain your intent, that might help.

POSTED BY: Eric Rimbey
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago

Okay, I think I see what you're saying now. I'll try to take a stab at it later. But in the meantime, here's the simplest thing I can think of to satisfy your suggestion to

take a list, turn it to a sequence before passing to f, then reconstruct it inside f, so that I can pass lists to other functions that I call from within f.

In fact, I'll show both "directions":

functionList[list_List] := dependencySeq @@ list;
functionList[{1, 2, 3}]
(* dependencySeq[1, 2, 3] *)

functionSeq[args___] := dependencyList[{args}];
functionSeq[1, 2, 3]
(* dependencyList[{1, 2, 3}] *)

inputList = {a, b, c};
functionSeq @@ inputList
(* dependencyList[{a, b, c}] *)

My intuition is that this is too simplistic to work, but without going back to look at eqnlist or ffoutsuper right now, that's the simplest thing that satisfies my best attempt to interpret your words here.

POSTED BY: Eric Rimbey
Posted 1 year ago

Okay, maybe this is what you're after. I changed variable names to help myself keep track of what's going on.

avInit = {1, 1};
avFns = Array[av, 2];
avFnsApplied = Through[avFns[t]]; (* {av[1][t], av[2][t]} *)

avProtoList[a_List] := 0.5 (1 - a[[2]]) a[[1]]^2 + (1 - a[[1]]) a[[2]]^2 + a[[1]]  a[[2]];
avProto[args___] := avProtoList[{args}];

g1 = Derivative[1, 0][avProto];
g2 = Derivative[0, 1][avProto];

eqnlist = {
  Derivative[1][av[1]][t] == avProtoList[avFnsApplied], 
  Derivative[1][av[2]][t] == avProtoList[avFnsApplied], 
  av[1][0] == avInit[[1]], 
  av[2][0] == avInit[[2]]};
solDEsuper = NDSolve[eqnlist, avFnsApplied, {t, 0, 1.4}]

I didn't try to check that the result is correct. I just tried to do a formal transformation into what I think you're asking for.

POSTED BY: Eric Rimbey
Posted 1 year ago

Not quite. Maybe somehow include g1 and g2 in the eqnlist. As it is now, the equation does not have the Derivatives (with respect to the list arguments, not time), and also eqnlist has done the evaluation symbolically of f, instead of keeping it unevaluated.

POSTED BY: Iuval Clejan
Posted 1 year ago

Ugh. Got wrapped around the axle in my translation. What about this?

eqnlist = {
  Derivative[1][av[1]][t] == g1 @@ avFnsApplied,
  Derivative[1][av[2]][t] == g2 @@ avFnsApplied,
  av[1][0] == avInit[[1]],
  av[2][0] == avInit[[2]]}
POSTED BY: Eric Rimbey
Posted 1 year ago

But you get the idea, right? One function to deal with lists and another to deal with sequences.

POSTED BY: Eric Rimbey
Posted 1 year ago

So close! It works, but unfortunately eqnlist has evaluated the derivative of f symbolically. Recall that in the actual case this cannot be done, since f can only be evaluated numerically. If I recall, what happens if I don't use the "Real" hack (which gives me a "non-numerical derivative at t=0 when I have lists, but works without lists), which is also what will happen if I use your solution in the actual case, is that it keeps trying to evaluate Eqnlist symbolically, never even getting to evaluate NDSolve and just doesn't give me output.

POSTED BY: Iuval Clejan
Posted 1 year ago

Okay, then I need more information. I can't tell the difference between this and what you had previously that I thought you said was good.

Is there anyway you can simplify the problem? Like just give a simple function whose derivative is just dead simple to look at and immediately recognize and then provide an actual correct solution that I can use as a test case?

I feel like we're getting lost in implementation details, and I haven't actually figured out what you really want.

Also, some commentary with your examples would be helpful. I'm not sure what's working as desired and what's not. You seem to set up an example just to indicate that it's not what you want, but it seems to work, so I don't know what the problem is.

Or maybe work backward. Hand-roll the entire NDSolve expression. Just provide the literal inputs that you want that work for NDSolve. Then show me what you have as inputs to this whole process. From there, maybe I can figure out how to get from your inputs to the final NDSolve expression.

POSTED BY: Eric Rimbey
Posted 1 year ago
Posted 1 year ago

I'm sorry, this isn't helping me. Please, please just give me literal concrete expressions that I can work with. I know it's obvious to you, but I'm not grokking. So, like in your original example, is the following even correct?

NDSolve[
  {Derivative[1][av[1]][t] == 1.*av[1][t]*(1 - av[2][t]) + av[2][t] - av[2][t]^2, 
   Derivative[1][av[2]][t] == av[1][t] - 0.5*av[1][t]^2 + 2*(1 - av[1][t])*av[2][t], 
   av[1][0] == 1., av[2][0] == 1.}, 
  {av[1][t], av[2][t]}, 
  {t, 0, 1.4}]

Does that produce the result you want? If not, show me an NDSolve expression that does produce the result you want. Then we can work backward from there. I can't figure out if this is the right "end game" and you're struggling to figure out how to get there, or if this is the wrong "end game" and you don't know where things went wrong.

POSTED BY: Eric Rimbey
Posted 1 year ago

Hmm... I don't know how to make it simpler. If you look at the two big files I included, the first has no lists and it works, the second has lists and it doesn't work. The same is true for the simpler file I included originally, but in that one there is an f that can have its derivative be taken symbolically (but can be prevented from doing so by the "Real" hack). In the more complex files, in both cases there is an f that is not able to have its derivatives be taken symbolically. The code snippet you posted:

NDSolve[
  {Derivative[1][av[1]][t] == 1.*av[1][t]*(1 - av[2][t]) + av[2][t] - av[2][t]^2, 
   Derivative[1][av[2]][t] == av[1][t] - 0.5*av[1][t]^2 + 2*(1 - av[1][t])*av[2][t], 
   av[1][0] == 1., av[2][0] == 1.}, 
  {av[1][t], av[2][t]}, 
  {t, 0, 1.4}]

has explicit expressions for the equations in eqnlist (effectively evaluating derivatives of f), so it it NOT what is needed.

POSTED BY: Iuval Clejan
Posted 1 year ago
POSTED BY: Eric Rimbey
Posted 1 year ago

Now you're just being funny, or sarcastic? I guess I need to think of a simple example of a function that can't be evaluated symbolically for you to "grok" what is going on. The example in those two files (nestedlevels4.3 and nestedlevels4.5 was too complicated apparently). This is probably just as frustrating for you as it is for me.

POSTED BY: Iuval Clejan
Posted 1 year ago

I'm very serious. I opened the notebooks, saw how large they were, and closed them. I'm not spending my day reverse-engineering all of that code.

You don't need to find a simpler function (although, I don't know why that's such a big deal), you just need to tell me what the "answer" is. Something I can use as a reference to check against. I'm not going to try to figure out the mathematics and I'd rather not reverse engineer a ton of code. I'm pretty sure there is a simple code issue here, but I'm tired of guessing. So, just show me exactly what a correct result would look like so that we don't just keep wasting time.

Look, you're the one with a problem to solve. I'm just trying to help out. My only investment here is just being curious about solving the programming problem. If it's not worth it to you to give me something explicit I can use as a test case, then it's not worth it. I can just move on to the rest of my life.

POSTED BY: Eric Rimbey
Posted 1 year ago

You might find a larger audience over at https://mathematica.stackexchange.com/ . Someone over there might just understand what you're asking for. Sorry I couldn't be of more help.

POSTED BY: Eric Rimbey
Posted 1 year ago

You said

The first shows that as long as there are no lists in the arguments to f, NDSolve is happy and so am I.

and

You can see from these 2 files that f has no hope of being evaluated symbolically (maybe there is a clever way to do it using certain symmetries, but I will have cases in the future where these symmetries are not present that might make f be evaluatable symbolically)

I know you think you're being helpful, but this is just a distraction. I need to know the actual things you want to pass in to NDSolve. Just forget about the list versus non-list arguments for now. I just need to have a target to shoot for. These informal descriptions are just not helping me.

I'm like 95% sure this is doable in a very easy way, but so far I've failed to hit your target, and I don't know what my errors are. Don't tell me in words like "evaluate f symbolically". Just give me a concrete target that I can shoot for.

POSTED BY: Eric Rimbey
Posted 1 year ago

Maybe! Just to make sure I understand your suggestion, here is the test file I implemented it in. Do I need to combine this with your previous suggestions on the 2-way conversions of lists to sequences, so I can pass varex (there will be several of these) to other functions from f? I fear there will be scoping problems, as varex is global and not scoped within f (I could be wrong about this). All the lists need to be updated as NDSolve chugs along.

Also, I need to figure out how to automate taking derivatives before forming eqnlist. I was using such constructs as this, when I was hoping I can pass f lists directly, to differentiate f for forming eqnlist:

nderaR[i1_, i2_, m_, 
   f_] := (Derivative @@ {0, 0, ConstantArray[0, m], 
      ConstantArray[0, m], ConstantArray[0, {m, m}], 
      Normal[SparseArray[{{i1, i2} -> 1}, {m, m}]], 0, 
      ConstantArray[0, m], ConstantArray[0, m], 
      ConstantArray[0, {m, m}], ConstantArray[0, {m, m}]})[f];

one for each list

POSTED BY: Iuval Clejan
Posted 1 year ago

Do I need to combine this with your previous suggestions on the 2-way conversions of lists to sequences, so I can pass varex (there will be several of these) to other functions from f?

I don't think so. Just my opinion here, and I obviously am not an expert in the domain you're working with, but it seems to me to think of f and whatever other functions you want to take partial derivatives of as being a function of multiple variables (arguments in sequence) rather than a single list argument. So, I would write these functions that way (e.g. f[x_,y_,z_] := ...) and deal with the List <-> Sequence stuff somewhere else. All I was really showing you before is that Apply (shortcut @@) is how you replace the List head with your function f. So, the Derivative[0, 1][f] @@ varex bit is the key to that.

I fear there will be scoping problems, as varex is global and not scoped within f

I don't think that's a problem based on what I've seen so far. If you think there's a risk of that somewhere, point it out to me and I'll analyze it. Scoping in Mathematica isn't really true scoping, it's just playing games with names, so what we'd really be concerned about is actually name collisions.

All the lists need to be updated as NDSolve chugs along.

Not sure what you mean by this. We can't really control what's going on inside NDSolve, so I'm not sure what you're getting at.

Also, I need to figure out how to automate taking derivatives before forming eqnlist.

Yeah, I figured at some point we'd need a way to automate this. That should be doable, but honestly I'm confused by your derivatives. For example, if I evaluate this

nderaR[2, 1, 2, g]
(* Derivative[0, 0, {0, 0}, {0, 0}, {{0, 0}, {0, 0}}, {{0, 0}, {1, 0}}, 0, {0, 0}, {0, 0}, {{0, 0}, {0, 0}}, {{0, 0}, {0, 0}}][g] *)

I don't know how to interpret that result. I think I just don't have the mathematical knowledge, but I don't know what having all those sublists in Derivative means. But setting that aside, if you can articulate the rules for how to create that expression, we can figure out how to generalize that.

POSTED BY: Eric Rimbey
Posted 1 year ago

OK, should be able to work on it tonight and tomorrow. I will let you know if it works, and if you're amenable I still want to give you that financial reward. Thanks!

POSTED BY: Iuval Clejan
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago

Yep, pretty sure we can take lists and transform them to sequences for Derivative.

POSTED BY: Eric Rimbey
Posted 1 year ago

In your third cell, why don't you just do the following?

f[a : {_Real, _}] := 0.5 (1 - a[[2]]) a[[1]]^2 + (1 - a[[1]]) a[[2]]^2 + a[[1]] a[[2]]
POSTED BY: Eric Rimbey
Posted 1 year ago
POSTED BY: Eric Rimbey
Posted 1 year ago
POSTED BY: Iuval Clejan
Posted 1 year ago

I'm not at all sure what you're trying to do, but here are some thoughts to get you started:

f1[args__] := {args}[[1]] + {args}[[2]];
f1[1, 2]
(* 3 *)

f2 = #1 + #2 &;
f2[1, 2]
(* 3 *)

Or, you can just overload your function:

f3[list : {_, _}] := list[[1]] + list[[2]];
f3[a1_, a2_] := f3[{a1, a2}];
f3[1, 2]
(* 3 *)

Or you can change the way you apply your function:

f4[a1_, a2_] := a1 + a2;
f4 @@ {1, 2}
(* 3 *)

But I don't understand why you think using Table and Array is creating some sort of impedance, so I don't have confidence in any of these approaches.

POSTED BY: Eric Rimbey
Posted 1 year ago

Thanks Eric for your help. I don't think I expressed what I need well, so your examples probably won't work. Here is a better explanation referring to the included notebook: The first evaluation cell has no problem because f1 and f2 are symbolically defined.

The second evaluation cell also has no problem because f1 and f2 are able to be symbolically evaluated in eqnlist, before being passed to NDSolve, by taking a symbolic Derivative of f.

The 3rd cell has a problem because I insist that the Derivative not be evaluated unless one of the arguments to f is a real number (instead of a symbol), which is the case once NDSolve gets going, but not initially. Though in this case f1 and f2 can be symbolically evaluated, in my real case they can't. If they are numerically evaluated before being passed to NDSOlve, then NDSolve will not get a list of symbolic equations. So they need to delay evaluation till NDSolve gets going (The real f is a conditional eigenvalue of a complicated large rank matrix that also depends on solving another ODE).

In the fourth cell, I show that as long as there is no list in the arguments of f, there is no problem with the same insistence on not evaluating eqnlist and keeping it symbolic so NDSolve is happy.

In the fifth cell, I try to convert the list to a sequence before passing to f, and then convert back to a list inside of f (It looks silly in this example, but in the real application keeping list structures is essential, there are many lists, they get passed on to other functions, and it becomes an unwieldy mess without them), but it doesn't work (I need to do the Extract while stripping the "Real" qualifier, I don't know how to do that yet)

POSTED BY: Iuval Clejan
Posted 1 year ago

So, which of these 5 examples is closest to what you actually want? You're demonstrating things that work, but apparently they aren't good enough. So what is your actual situation and what is it that you're actually trying to get to work?

POSTED BY: Eric Rimbey
Posted 1 year ago

The third cell would be ideal since it keeps the list structure. The fifth cell tries to get rid of the list structure (in reality I would also Flatten some Joined lists, some of which are also 2 dimentional, nested lists, to create args, I didn't bother showing that in this example), but it's far from ideal and also a hack, to compensate for the bug that Derivative can't handle qualifiers in lists. The other examples that actually work, either do not have lists (in arguments to f), or have functions that can be evaluated symbolically (and their derivatives too), which is not my case in the actual application. And I haven't gotten the fifth cell to work yet, though maybe it is close?

POSTED BY: Iuval Clejan
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard