Message Boards Message Boards

11
|
29948 Views
|
6 Replies
|
15 Total Likes
View groups...
Share
Share this post:

Computing Cellular Automatons with the Wolfram Cloud in an Android app

Posted 11 years ago

Before beginning this section, be sure that you've read through my first post. Much of the code used there will be reused here.

Creating an Android Application That Uses the Wolfram Cloud

In this section, we will expand on the first tutorial by showing how to create and use APIFunctions that use multiple inputs, do basic error checking, and how to receive images as inputs in the Android app. To do this, we will create a sample app that will display a basic cellular automaton pattern given a rule and number of steps. If you are unfamiliar with cellular automaton, you can read more about them here. The source code for this project, along with a Mathematica Notebook containing the API Function, can be downloaded from here.

Creating the API Function

This API Function, like in the first tutorial, will contain 3 parts: the input, a pure function, and an output. This time, however, we are using 2 inputs rather than one. To do this, create 2 associations separated by a comma. The pure function will also be larger than last time, as we will also do some basic error checking to ensure reasonable input is given. Since the basic cellular automaton we are dealing with can only be defined by 256 rules, we will use and If function to check that the "rule" input is not less than 0 or greater than 255. If the API sees this input, it will return an error message. Either way, we will give the output as a PNG image.

api = APIFunction[
   {"rule" -> "Number", "steps" -> "Number"},
   If[
     Or[#rule < 0, #rule > 255],
     Style[Framed["(Invalid Rule #)", ImageMargins -> 0], 45, Red],
     ArrayPlot[CellularAutomaton[#rule, {{1}, 0}, #steps]]
     ]&,
   "PNG"
   ];

This APIFunction can be deployed with a specific name and file path, so that the deployed function will appear in a specific place within your personal Wolfram Cloud file directory using a CloudObject:

obj=CloudObject["Samples/CellularAutomaton"];
CloudDeploy[api, obj, Permissions->"Public"]

This will output a link to the deployed API form, just as before:

https://www.wolframcloud.com/objects/user-4d6d3309-8894-48e9-b3bd-c49322e869c9/Samples/CellularAutomaton

Notice that in the middle of the URL is the word "user" followed by an alphanumeric signifier unique to every Wolfram Cloud account. If you were to deploy this API function yourself, that portion of the URL would be different.

With the API function complete and deployed, let's create the Android application.

Making the Android Application

In order to work with this new API function, a few changes have to be made. First, we must give the user a way to give 2 different inputs:

Cellular Automaton Screenshot

The other change that must be made is to specify that the Android app is expecting to get back an image rather than text. First, let's look at CloudInterface:

public interface CloudInterface {
    public void onEvaluateCompleted(Bitmap result);
    public String getBaseURL();
}

The only difference here is that the parameter in onEvaluateCompleted changed from String to Bitmap. This change must be reflected in MainActivity, but we will see that later. First, let's look at CloudCompute.

This method is actually a bit shorter than it was when receiving a string. This is because Android has a built-in BitmapFactory class that handles images very cleanly. First, this Cloud Compute class takes an int array rather than a single Double object. The URL string is constructed by using "?rule=", similar to before. Since there is a second input, we must also include "&steps=" to designate the second input being used. Exactly as before, a URL object is created using the URL string. Rather than creating a BufferedReader to read the input stream from the URL, however, we use the BitmapFactory mentioned previously. Then, we use onPostExecute to pass the resulting Bitmap back to MainActivity.

public class CloudCompute extends AsyncTask<int[], Void, Bitmap> {
    private String baseURL = "";
    private CloudInterface callback;

    public CloudCompute(Context c) {
       this.callback = (CloudInterface) c;
       baseURL = callback.getBaseURL();
    }

    @Override
    protected Bitmap doInBackground(int[]... params) {
       int rule, steps;
       Bitmap bitmap = null;

       for (int[] pair : params) {
         try {
          rule = pair[0];
          steps = pair[1];

          baseURL = baseURL + "?rule=" + rule + "&steps=" + steps;

          URL url = new URL(baseURL);

          bitmap = BitmapFactory.decodeStream(url.openStream());

         } catch (Exception e) {
          e.printStackTrace();
         }
       }
       return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap result) {
       callback.onEvaluateCompleted(result);
    }
}

In the MainActivity class, fewer changed are required. First, 2 EditText fields are required to get both of the inputs. The ints parsed from these fields are put into an array to give to Cloud Compute. Last, in the onPostExecuted method, we use setImageBitmap to display the output instead of setText.

public class MainActivity extends Activity implements CloudInterface {
    private EditText prompt1, prompt2;
    private ProgressBar bar;
    private ImageView imageView;

    private String baseURL = "https://www.wolframcloud.com/objects/user-4d6d3309-8894-48e9-b3bd-c49322e869c9/Samples/CellularAutomaton";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       prompt1 = (EditText) findViewById(R.id.prompt1);
       prompt2 = (EditText) findViewById(R.id.prompt2);
       bar = (ProgressBar) findViewById(R.id.progressBar);
       imageView = (ImageView) findViewById(R.id.image);
    }

    public void buttonClick(View v) {
       bar.setVisibility(View.VISIBLE);

       imageView.setImageResource(android.R.color.transparent);

       InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
       imm.hideSoftInputFromWindow(prompt2.getWindowToken(), 0);

       int rule = Integer.parseInt(prompt1.getText().toString());
       int steps = Integer.parseInt(prompt2.getText().toString());

       CloudCompute cc = new CloudCompute(this);

       int[] pair = { rule, steps };

       cc.execute(pair);
    }

    @Override
    public void onEvaluateCompleted(Bitmap result) {
       bar.setVisibility(View.GONE);
      imageView.setImageBitmap(result);
    }

    @Override
    public String getBaseURL() {
       return baseURL;
    }
}

With these changes made, the app is ready to be used. If you want to see this app in action, we've uploaded a short video of it to YouTube. And again, the code for this app can be downloaded from here.

POSTED BY: Brett Haines
6 Replies

Hello Kyle! It does seem like there is a lot of code here, but once you break it down it can become more manageable. If you download the zip file linked in the article, the Java files have line-by-line comments, which should make everything a bit easier to understand.

As for what kind of proficiency you need, I wouldn't say it is too much. Moving past the intimidation is the hardest part, for which I would recommend jumping right in. Once you start getting things working, it gets much easier.

The resources I turn to pretty commonly are the Android developer's training pages, the Vogella tutorials, thenewboston's Android and Java tutorials, and Stack Overflow when you're stuck.

And definitely have fun coding!

POSTED BY: Brett Haines
Posted 11 years ago

Hi Brett. Amazing examples! I'm tempted to build an MMA app but I'm intimidated by all the Java codes I've seen. How much proficiency in Java should I have to build apps like this? Lastly, what resource would you recommend a complete newbie to mobile app design to read?

POSTED BY: Kyle Nguyen

This would probably be easiest to fix by surrounding steps and rule with the Round function, or by checking the head of each in the If conditional to see if they are Integers. But either way, good catch!

POSTED BY: Brett Haines

Very nice! One minor tweak might be to validate the user input to only accept integer-valued parameters:

https://www.wolframcloud.com/objects/user-4d6d3309-8894-48e9-b3bd-c49322e869c9/Samples/CellularAutomaton?rule=41.5&steps=30

Attachments:
POSTED BY: Andrew Walters

App-side, a radio button group would probably be the easiest (and most user-friendly) way to do that. In the API function, this wouldn't be difficult to implement at all. We could add a third input to the input association list

"random"->"Boolean"

then use that in conjunction with another If command:

If[#random, 
    ArrayPlot[CellularAutomaton[#rule, RandomInteger[1, 100], #steps]],
    ArrayPlot[CellularAutomaton[#rule, {{1}, 0}, #steps]]
 ]

replacing the single ArrayPlot command that currently exists in the API.

Edit: If you use RandomInteger[1, #steps] instead, it will make the resulting evolution a square every time. This might save you some really poorly shaped images.

POSTED BY: Brett Haines

Perfection!

This is already great. But it would be so cool to actually have a choice of random initial condition (a vector of 0 and 1 randomly populated) or single cell one that you already did. Would this be hard to implement? Should be control a radio-button or something else in the app?

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

Group Abstract Group Abstract