1
$\begingroup$

I am developing a simple input/output dialog, where the input is a string typed in by the user and the output is both graphical and textual. I think some of the answers to this question may be found in this very interesting post Basic questions about GUI, but before I spend time studying it I think I need to resolve a higher-level issue. The problem is that I need to search through a large array of names to match the input string. Once a match is found, the corresponding data needs to be displayed as a DateListPlot, and I would like the plot to show up just below the InputField or whatever other textbox widget works. Other textual information such as Fourier/Wavelet coefficients could then be displayed below the plot (following a suitable backend calculation that I have not set up yet) to provide additional information about the data.

I have implemented this using DynamicModule and it sort of works, as long as the array I am searching is not too large, say less than 500 names. My array is on the order of 10000, and this is causing Dynamic to choke. This is not surprising, and in fact there seems to be mismatch between what I want to do, which seems relatively straightforward even if CPU-intensive, and what Manipulate and Dynamic are meant to be used for, i.e. low-CPU calculations for dynamic/interactive visualisation of graphics. What I have to do is basically static (although some of the things I saw here made my mouth water: How to manipulate 2D plots?), but needs to support communication between some user input and a backend calculation, with consequent update. In other words I think I may be using the wrong tool for the job, but I don't know if Mathematica has the right tool. What I am building is a prototype that will eventually be re-implemented in some appropriate language and will be displayed in a browser, but to even see if the prototype is worth pursuing I need it to work at some basic level, and it's proving a lot harder than I expected.

For completeness here is the code that sort of works (I posted something similar not too long ago, addressing a different question):

accountBalanceStepFunction = Table[{0},{i,1,10}];
nameWithAccount = Table[{0},{i,1,10}];
accountBalanceStepFunction[[1]] = {{3565987201, 200.`},{3568548037, 200.`},
{3568548037, 1200.}, {3568548115, 1200.}, {3568548115, 2200.}, 
{3575725277, 2200.}, {3575725277, 2570.}, {3579424395, 2570.}, 
{3579424395, 1917.}, {3588060863, 1917.}, {3588060863, 2067.}, 
{3590150126, 2067.}, {3590150126, 2417.}, {3598955894, 2417.}, 
{3598955894, 1978.}, {3600589624, 1978.}, {3600589624, 2533.}, 
{3603185560, 2533.}, {3603185560, 2094.}, {3613294128, 2094.}, 
{3613294128, 2974.}, {3621255776, 2974.}, {3621255776, 2535.}, 
{3627130885, 2535.}, {3627130885, 2096.}, {3632042062, 2096.}, 
{3632042062, 1657.}, {3636116493, 1657.}};
accountBalanceStepFunction[[2]] = {{3578213180, 1166.}, {3578213295, 1166.},
{3578213295, 3208.}, {3579362464, 3208.}, {3579362464, 3800.}, 
{3580472918, 3800.}, {3580472918, 3542.}, {3580550375, 3542.}, 
{3580550375, 5691.}, {3582006102, 5691.}, {3582006102, 6820.}, 
{3585376966, 6820.}, {3585376966, 6257.}, {3590994393, 6257.}};
nameWithAccount[[1]] = "John Doe";
nameWithAccount[[2]] = "Mary Smith";
NAccounts = 10;
Panel[
 DynamicModule[
  {n1 = 1, nameUser = "John Doe", m1 = 1, m2 = NAccounts},
  Column[
   {
    InputField[Dynamic[nameUser], String, FieldSize -> 50],
    Style[
     Dynamic[
      Do[
       If[nameWithAccount[[i]] == nameUser,
        n1 = i;
        Break[]],
       {i, m1, m2}
       ], SynchronousUpdating -> False (* added this later *)
      ], Transparent],
    Dynamic[
     DateListPlot[{accountBalanceStepFunction[[n1]]},
            PlotRange -> All,
            Joined -> True,
            GridLines -> Automatic,
            DateTicksFormat -> {"Day", "/", "MonthShort", "/",              
                                "YearShort"},
            PlotLabel -> "Balance History",
            ImageSize -> 400,
            Background -> White,
            PlotStyle -> {{Thickness[.007], RGBColor[1, 0, 0]},
                            {Thickness[.007], RGBColor[0, .7, 0]},
                            {Thickness[.007], RGBColor[0, 0, 1]}},
            AxesLabel -> {Style[ " t", Large, Bold, Italic], 
        Style["Balance", Bold]},
                LabelStyle -> Directive[FontSize -> 13, Bold],
            PlotLegends -> SwatchLegend[{nameWithAccount[[n1]]},
        LegendMarkers -> 
         Graphics[{EdgeForm[Black], Opacity[1], Rectangle[]}]]]
     ]}
   ]
  ]
 ]

When NAccounts is 500 or less this more or less does what I need, for a few thousands or above it gets very sluggish and around 10000 Mathematica just gives up, it tells me Dynamic is taking too long to update.

Thank you in advance.

2nd Edit: moved m1, m2 inside DynamicModule, added [] to Break (this was a proper bug)

$\endgroup$
  • 2
    $\begingroup$ You need to read tutorial/AdvancedDynamicFunctionality/Synchronous versus Asynchronous Dynamic Evaluations and IntroductionToDynamic for completness. $\endgroup$ – Kuba Dec 7 '16 at 12:00
  • $\begingroup$ Find hard to help. please include the import code and a sample data file with a few rows with the appropriate fields. $\endgroup$ – Jose Enrique Calderon Dec 7 '16 at 12:19
  • $\begingroup$ @JoseECalderon, thanks, I have added some example data, for just two possible curves, the rest are empty. $\endgroup$ – pdini Dec 7 '16 at 16:49
  • $\begingroup$ Thanks @Kuba, I had read those chapters some time ago but I read the section you point to again today and it made more sense. I can see that I did not use the concept of Dynamic correctly in my question. It's not entirely clear to me yet whether it is going to work, but if you are pointing me to it I am guessing you think it should. So I will read the section again and think about it some more; I may then edit my question and will in any case give you an update. $\endgroup$ – pdini Dec 7 '16 at 16:54
  • $\begingroup$ @pdini I must say I wasn't focusing on details of your code but the case and symptoms you described fit that section well :) $\endgroup$ – Kuba Dec 7 '16 at 17:03
0
$\begingroup$

Edit: adding some missing quote marks

I prefer using functional code (so Map rather than Do), and Dataset instead of List - I appreciate that if you want to use a different language ultimately this may not work but this is an effective solution when using the Wolfram Language. Here's how I would implement the searcher (I have done similar operations with half a million entries and not had significant performance issues so it can work smoothly):

First make some random data to test with:

accounts = Dataset[
   Table[
    <|
     "Name" -> name[EntityProperty["Pokemon", "Name"]], 
     "Account Balance" -> 
      Table[{RandomInteger[{3578213180, 3590994393}], 
        RandomReal[{1166.`, 6820.`}]}, {14}]
     |>,
    {name, EntityList["Pokemon"][[;; 400]]}
    ]
   ];

Make a search function:

search[dataset_,text_]:=First[
  Cases[Normal[dataset[All, "Name"]],x_/;StringMatchQ[x,text~~___]],
  $Missing
];

Implement the DynamicModule:

Panel[
 DynamicModule[
  {
   nameUser = "Bulbasaur"
   },
  Column[{
    InputField[Dynamic[nameUser], String, FieldSize -> 50],
    Dynamic@With[{foundname = search[accounts, nameUser]},
      With[{data = 
         accounts[Select[#Name == foundname &], "Account Balance"]},
       If[
        ListQ[Normal@data],
        DateListPlot[
         data,
         PlotRange -> All, Joined -> True, GridLines -> Automatic, 
         ImageSize -> 400, Background -> White,
         DateTicksFormat -> {"Day", "/", "MonthShort", "/", 
           "YearShort"},
         PlotLabel -> "Balance History",
         PlotStyle -> {{Thickness[.007], 
            RGBColor[1, 0, 0]}, {Thickness[.007], 
            RGBColor[0, .7, 0]}, {Thickness[.007], RGBColor[0, 0, 1]}},
         AxesLabel -> {Style[" t", Large, Bold, Italic], 
           Style["Balance", Bold]},
         LabelStyle -> Directive[FontSize -> 13, Bold],
         PlotLegends -> 
          SwatchLegend[{foundname}, 

           LegendMarkers -> 
            Graphics[{EdgeForm[Black], Opacity[1], Rectangle[]}]]
         ]
        ]
       ]]
    }]
  ]
 ]

Example tool

$\endgroup$
  • $\begingroup$ Thank you very much @lowriniak. I don't mind the functional notation, except that I can't separate it from any algorithmic improvements you answer apparently contains. Is your code fast because of (1) the Dataset data structure, (2) the search function you defined, (3) the functional code itself, (4) some other reason, or (5) some combination of the above? I am more than happy to study your code and unpack it layer by layer but it would help me to separate the syntax from the performance. Thanks in advance! $\endgroup$ – pdini Dec 8 '16 at 20:07
  • $\begingroup$ I went through your code and I actually understand it. I've used some of the simpler forms of MMA functional programming before and am getting used to them. The only thing I could not verify is the pattern First[ Cases[ ... ], $Missing] in the definition of the search function. I tried it on a simple list and got an error msg: "First::argx: First called with 2 arguments; 1 argument is expected". If you can explain what that $Missing is doing there I'd appreciate it, but the bigger question remains: why is this faster than procedural code? I really need to understand this... thx $\endgroup$ – pdini Dec 9 '16 at 14:46
  • $\begingroup$ Sorry to keep spamming you but I found two more things. I have MMA 10.0.2.0, which may or may not matter. In any case, the $Missing in your code gave me an error, so I got rid of it and got past that point. Second, I think you put in a bug in the search function: Name should not be capitalised, I think it should be name. This seemed to work better, in the sense that now I did not get an error message (with Name, data was empty as in {}), but the plot still did not display. The panel came out, then the InputField with the default name, then $Aborted. Any thoughts? thx $\endgroup$ – pdini Dec 9 '16 at 15:50
  • $\begingroup$ Are you on version 10 or earlier? That First notation is new in 11, it means 'if there is no first element, return $Missing instead'. You could probably implement that with If[ Length[ list ] < 1, $Missing, First[ list ]. $\endgroup$ – lowriniak Dec 9 '16 at 16:05
  • $\begingroup$ The speed improvements is mostly due to the way that the Wolfram Language works. Functional code is almost always compiled and optimised internally so it can run much more efficiently, whereas procedural code follows exactly the procedure that is described (even when it could be optimised) which is often far more inefficient. If you were to compile the loops manually (Compile can be used on certain low level constructs), it may be faster but it takes a lot more work on the user's part. $\endgroup$ – lowriniak Dec 9 '16 at 16:05
0
$\begingroup$

The solution turned out to be a lot simpler than I expected, and it also allows the use of Manipulate: CreatePalette! I am posting my procedural code here, although @lowriniak's syntax is much nicer:

m1 = 1;
m2 = NAccounts;
CreatePalette[
 Manipulate[DateListPlot[{accountBalanceStepFunction[[n1]],
                            accountBalanceStepFunction[[n2]],
                            accountBalanceStepFunction[[n3]]},
        PlotRange -> All,
        Joined -> True,
        GridLines -> Automatic,
        DateTicksFormat -> {"Day", "/", "MonthShort", "/", "YearShort"},
        PlotLabel -> "Saldi",
        PlotStyle -> {{Thickness[.004], RGBColor[1, 0, 0]},
                        {Thickness[.004], RGBColor[0, .7, 0]},
                        {Thickness[.004], RGBColor[0, 0, 1]}},
        AxesLabel -> {Style[ " t", Large, Bold, Italic], 
     Style["Balance", Bold]},
            LabelStyle -> Directive[FontSize -> 13, Bold],
        PlotLegends -> SwatchLegend[{nameWithAccount[[n1]],
                                    nameWithAccount[[n2]],
                                    nameWithAccount[[n3]]},
            LegendMarkers -> 
      Graphics[{EdgeForm[Black], Opacity[1], Rectangle[]}]],
        ImageSize -> {700, 400}],
    {n1, m1, m2, 1}, {n2, m1, m2, 1}, {n3, m1, m2, 1},
  ContentSize -> {1100, 500}],
 WindowSize -> {1110, 700}
 ]

One thing I have not tried is the InputField, but the main point is that CreatePalette creates a separate notebook that is interactive but does not slow down the front end, which is what I needed.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.