How to Use Parquet with MapReduce

Parquet is a great file format for use with higher level tools like Impala, Hive, Pig, and Spark.  But what if you want to use it in MapReduce?  Cloudera provides an easy to follow example on how to do this, and is a perfect guide for basic usage of the Parquet MapReduce API.  As an enhancement, more speed can be gained by using a different object model.

The Parquet SimpleGroup toString() method, which is what is utilized in MapReduce when using the default Parquet object model, is extremely slow.  I had a client recently with a job that was taking over an hour to run with only 1.9 GB of data (18 GB uncompressed) because they were following the sample code.
The solution to this problem is to use a different in-memory object format.  There are two pieces to using Parquet: the object format and the storage format.  Parquet provides its famous binary columnar storage format and excellent compression.  It also provides an object model in the form of the “example” Group class.  Other object models exist, though, including Avro, Google Protocol Buffers, Thrift, Hive, and Pig.  You still get the benefits of Parquet’s efficient storage mechanism, but you get the added benefit of a more robust and versatile in-memory object model to manipulate data after you’ve loaded it.

After moving the object model for this particular client to Avro, the job duration dropped to under 7 minutes.  Check out how to use Parquet with an Avro object model instead on my GitHub and let me know if you have any questions.

Where People Run: Redux

I recently came across this article that provided some beautiful visualizations showing where people run using RunKeeper data.

At the end of the article a link is provided to an R script so you can plot your own runs.  Awesome!  Except… some things were a bit off.

After downloading my RunKeeper data from here I ran the script.  I was presented with a PDF containing a bunch of lines and no map.

RunKeeperScriptFromFlowingData

That’s not entirely useful, so I modified the script to download a map automatically and plot the routes on the map.

The other issue that I found was that only the first segment of your run was imported using the script, so if you routinely pause RunKeeper during your run (or have it do that automatically when you come to a stop) then you will have issues.  So I’ve also modified the script to read all segments from each run instead of just the first.

Here’s the final result, where you’ll notice a nice map and many more tracks present.

RunKeeperScriptAfter

The script is provided for you here.  Just make sure you modify the LEFT, BOTTOM, RIGHT, and TOP variables to bound your area of interest.

map-routes

How To Align Partitions on an Upgraded iPod Hard Drive in Windows

If you’ve upgraded your iPod using a newer, larger drive, there’s a chance that the new drive uses “Advanced Format” sectors.  Drives that are use Advanced Format have sectors larger than 512 bytes; generally 4K bytes.

If you upgraded your iPod using one of these drives, it will work, but performance will be degraded significantly.  This is because iTunes partitions the drive inside of your iPod with no consideration about whether it is an Advanced Format drive or not.  iTunes uses two partitions when formatting:

  1. The first partition is the “system” hidden partition.  This is where the iPod stores its operating system.
  2. The second partition is free space formatted as FAT32.  This is the partition we are concerned about.

I upgraded my 5G iPod Video 60GB to a 120GB drive using a Samsung HS12YHA, but I was really disappointed when syncing took forever and write speeds were less than 1MB/s.  This occurs when the logical sectors on the disk are unaligned with the physical sectors.  The iPod uses 2K sectors.  Here’s a diagram to show what I mean:

diagram that shows the different between aligned and unaligned sectors

If you take a look at the unaligned picture you can see that when the OS writes to disk (using logical sectors, as usual) it’s possible that the logical sector spans across two physical sectors.   Because a sector is the smallest unit that a disk can address, when a sector is written to the entire sector needs to be read, the change made, and then the sector written back to disk.  As such, if you change a logical sector that spans across multiple physical sectors, then both physical sectors need to be read entirely, changed, and written to disk again causing the drive to do a lot of extra work.

What we want to achieve is a drive that has its logical sectors aligned with its physical sectors.  To do this, the start of the data partition on the iPod needs to start at a physical sector.  Here’s how to do it on Windows.

WARNING: YOU WILL LOSE ALL DATA ON YOUR IPOD WHEN DOING THIS.  ALSO, BE EXTREMELY CAREFUL THAT YOU DO NOT EDIT THE WRONG DISK.  I am not responsible if something goes wrong.

  1. Restore your iPod using iTunes.
  2. Download Symantec’s Partition Table Editor, PTEDIT32, from this link.
  3. Open PTEDIT and select your iPod from the drop down menu.  You can do this by matching the disk size with your iPod side.  You may have to start PTEDIT as an administrator by right-clicking it and choosing “Run as administrator”.
  4. You now need to figure out which sector partition 2 should start on.  DO NOT alter partition 1!  To determine which sector to use, increase the number in “Sectors Before” for partition 2 until it is divisible by 8.  Keep track of how many sectors you add!  For instance, if your “Sectors Before” field reads 224910 (as mine did) you would increase this to 224912 because 8 divides evenly into it.
  5. Add the same number of sectors to the Starting Sector field for partition 2.  My disk started at sector 1, so I increased this to 3.
  6. Subtract the same number of sectors from the Sectors field for partition 2.  My partition had 234216737 sectors, so I made this 234216735.
    DO NOT alter any other fields!
    Here’s what my final partition table looked like:
  7. Save your changes.  Exit PTEDIT and “safely remove” your iPod.
  8. The iPod should reboot, but it’s probably going nuts trying to understand what happened to the partitions, as you’ve destroyed the file system.  You’ll need to boot the iPod into Disk Mode to continue.
  9. Connect the iPod to the computer.
  10. Download fat32format and extract it somewhere convenient.
  11. Open a command prompt and navigate to the spot where you extracted fat32format.
  12. Make SURE you type this command correctly.  Type:

    fat32format IPOD_DRIVE_LETTER_HERE:

  13. This will quickly format the iPod’s data partition.  When it’s done, continue.
  14. Disconnect the iPod and reconnect it to the computer.
  15. You should now be able to use your iPod at full speed!
As always, if you have any questions, post them here and I’ll do my best to help.

How to archive your Pidgin chat log files.

I am somewhat of a hoarder when it comes to my chat logs.  I routinely find them useful to go back to: “What was that website Bob showed me, again?”  “What was the grocery list my wife gave me?”  As such, I have around 23,000 logged chats.  Now while this may not seem like a problem, I use Dropbox to sync my logs to multiple machines, so that no matter where I am I have access to them.  It takes a long time for programs to index 23,000 small files, so I wanted to come up with a way to archive the logs.

Unfortunately Pidgin doesn’t make this easy, as the log files follow a folder naming convention like this:

logs/protocol/username/buddy name/2010-02-23…html

There’s no way to easily pick out, and keep the folder structure of your log files, so I wrote a Perl script to accomplish the task.

You’ll need Perl installed on your machine.  I like to use ActivePerl, which you can grab here.

Extract the package to a directory of your choosing.  Next, run the script with the following usage:

perl    ArchiveLogs.pl    path_to_your_logs_dir    year_you_want_to_archive    output_directory

For example, on Windows your Pidgin log files are located in the %AppData% directory.  Here’s what the command would look like on my machine:

perl    ArchiveLogs.pl    C:\Users\Brian\AppData\Roaming\.purple\logs    2010    C:\Users\Brian\LogBackups

This will copy all conversations from all of your IM accounts into the LogBackups/YEAR folder.  It will then automatically zip those files into an archive.

But what about the old log files?  The script has an option to delete your old files.  Use this ONLY if you have already archived your logs!

The syntax of the command is the same, but you replace the output directory with the word “DELETE”.  For example, if I wanted to delete my old 2010 conversations:

perl    ArchiveLogs.pl    C:\Users\Brian\AppData\Roaming\.purple\logs    2010    DELETE

That’s all there is to it!  However, as usual, USE THIS SCRIPT AT YOUR OWN RISK.  I make no guarantees that it’s bug free. 😉

Get the script here: ArchiveLogs.

Brian

New (and awesome) Programming Jargon

I stumbled across this post the other day on globalnerdy.com.

There are some real gems in here, and I’m sure those of you that have developed software before can relate to at least a few of them. Here are some of my favorites:


Yoda Condidions

The act of using:

if (constant == variable)

instead of:

if (variable == constant)

It’s like saying “If blue is the sky”.


Bugfoot

A bug that isn’t reproducible and has been sighted by only one person.


Hindenbug

A catastrophic data-destroying bug. Oh, the humanity!

Sync your iTunes library and settings across multiple computers on Windows Vista/7.

You will need Windows Vista or Windows 7, the newest version of iTunes, and a Dropbox account for this tutorial. A Dropbox referral link is provided in this article which allows you and I to both get more storage space! This is also doable on Mac OS X, but I do not have access to a machine for screenshots and testing. It is also doable with Windows XP, but the commands at the CLI are a bit different. I’ll update this later with that information.

Note that both computers must have the SAME user name.

REMEMBER: When working with your personal data it is IMPERITIVE that you back everything up before you start working. Anything can happen, and I cannot be held accountable for data loss.
Let’s just take the situation of 2 PCs, a laptop and a desktop. You want your desktop to function as a media server with all of your music for your laptop to access over the network. Let’s also assume that you have a separate hard drive to make things easier.

Let’s assume again that your media is stored on a separate drive than your iTunes library. I use my M: drive. We need to have this path mapped on the laptop to access the music across the network. Map the drive to the same drive letter on both PCs. There are many how-to’s on the net for reference here, so it won’t be covered.

1. Next, download Dropbox from here:
Dropbox

2. Install Dropbox using the instructions provided on their website. Once it is done installing and you have logged in and gone through the tutorial, come back here.

Welcome back.

Now, we are going to use Dropbox to sync our iTunes library AND settings between multiple computers. I like to have exactly the same settings and library on both computers.

3. The first step is to move our library files (not the music files) into the cloud! Go to your Music folder (C:\Users\USERNAME\Music) and CUT the iTunes folder.
(note, it is shown as a shortcut in the screenshot, it will not be on your computer)

1iTunesDropbox

4. Now navigate to your new Dropbox folder, by default at “C:\Users\USERNAME\My Dropbox” and create a folder called “iTunes”

2iTunesDropbox

5. Now go to that folder and paste your iTunes folder that you just cut from your Music folder.

3iTunesDropbox

6. Now time for some command prompt fun! Go to the start menu and type “cmd” then right click cmd.exe and click “Run as administrator”

4iTunesDropbox

7. Click “Yes” and you will see this:

5iTunesDropbox

8. Now type:

cd C:\Users\USERNAME\Music\
mklink /d iTunes “..\My Dropbox\iTunes\iTunes”

You will see:

6iTunesDropbox

Now, when you double-click the link you just created, it will bring you to your iTunes music folder. Remember you moved it to the Dropbox folder. But look at the address bar!

7iTunesDropbox

Windows looks at this as an actual folder, but every file here is actually one of the files in your Dropbox iTunes folder! Any file created, deleted, or modified here has the same done to it in the Dropbox folder.

9. Now go to your laptop and install Dropbox on this computer.

10. (Assuming iTunes is already installed) Go to your music folder on this computer (C:\Users\USERNAME\Music) and delete the iTunes directory.

11. Follow steps 6 – 8 again on this computer.

12. Go back to your desktop and go to the start menu. Type “%appdata%” and hit enter.

8iTunesDropbox

13. Navigate to “Apple Computer” and CUT the iTunes folder from inside of it.

14. Go to your Dropbox and make a new folder titled “AppData” and paste the iTunes folder into it.

15. Start up the command prompt again (step 6). This time, use the following commands:

cd “C:\Users\USERNAME\AppData\Roaming\Apple Computer”
mklink /d iTunes “..\..\..\My Dropbox\iTunes\AppData”

16. Go to your laptop and follow steps 12 – 15.

Now it’s a waiting game. Wait until the Dropbox tray icon has stopped being a blue “recycling” or “refresh” icon and has become a green “check” icon. At this point, fire-up iTunes. You should be ready to go.
This is doable without Dropbox, but the benefit is that all of the library files are stored locally on the hard drive. This means that access to changing song tags and browsing the library in general is MUCH faster than over the network. Dropbox does a binary diff between files and only uploads the bits that have changed, so when uploading a change to your library it is much smaller than the entire file.

Corrupted email and Outlook.

I had a customer the other day that was complaining about an error message that was coming up with her Microsoft Outlook. We had just added her AOL account to Outlook using IMAP, and she was getting the following error message:

Your IMAP server wants to alert you the following: Can’t read attachment for message [messagenumber]

I did a search on this message and couldn’t find much, but did find a few other similar messages. As it turns out, looks like some of her emails or attachments were corrupted on AOL’s servers, and when Outlook was syncing them it notified her. The reason she never noticed it before was because when you browse your mail on AOL, it only downloads the email that you click on, not the entire message (obviously).

Luckily, out of her thousands of emails, only 17 returned the error.

Brian

How are these not already MATLAB functions?

EDIT: I have since been informed that you can concatenate multiple arrays together just by putting them inside braces []. Thus, you can use MATLAB’s built-in min and max functions by doing the following:

% assuming A and B are two arrays…
C = min([A B]);

You learn something new every day when you’re a novice in some language!

Nevertheless, I will keep my two functions implemented. The downside to using the above method is that it results in a full-on copy of the two vectors into a single vector. The problem with doing this is that:
1) It is slow!
2) It could result in a memory overflow if the arrays are large enough.

I have also updated my functions to handle matrices as well. Enjoy.

=============================================================================

I was at work today and needed to loop through a large amount of data in MATLAB. I was doing a diff between two arrays and needed to loop between it’s min and max values. Unless I suck at using Google, I couldn’t find any built-in MATLAB functions that return the smallest min of multiple arrays or the largest max of multiple arrays.

Here they are for your pleasure:

smallestMin

% SMALLESTMIN(X) The smallest component of all vector and/or matrix inputs.
%   SMALLESTMIN(X), where X is any number of vectors and/or matrices, is 
%   the smallest component present in any of these structures.  
%   SMALLESTMIN(X) is consistent with MATLAB's built-in MIN(X) for vectors
%   only!! SMALLESTMIN(X) for a matrix returns the equivalent of 
%   MIN(MIN(X)).  SMALLESTMIN accepts mixed inputs of vectors and matrices.
%   A matrix is defined as having exactly 2 dimensions.
%
%   VALID INPUTS: row and column vectors and 2-D matrices.  These can
%                 contain characters, complex numbers, and numerics.
%                 Logicals, doubles, singles, int8, uint8, int16, int32,
%                 and uint32 are also valid.
%   INVALID INPUTS: cell arrays, 64-bit signed and unsigned values,
%   structures, function handles, user classes, and java classes.
%
%   These lists may not be all-inclusive.
%
%   Ex 1: If A = [1 2 3 4] and B = [-1   then smallestMin(A,B) returns -1.
%                                    2
%                                    3
%                                    4]
%
%   Ex 2: If A = [1 2   and B = [5 6 7 8] then smallestMin(A,B) returns 1.
%                 3 4]
%
%   Author:  Brian A. Schrameck
%   Version: v0.1
%   Date:    07/15/2009
%   Email:   <a href="mailto:schrameckbrian+smallestMin@gmail.com">schrameckbrian+smallestMin@gmail.com</a>
%
%   See also min, max, isnumeric, and largestMax (available on the MATLAB
%   File Exchange).

function smallestVal = smallestMin (varargin)

smallestVal = 0;

if ( length(varargin) < 1 )
    error('At least 1 argument must be present!')
end

for i=1:length(varargin)
    % This will catch at least most of the bad stuff thrown at it.  It will
    % not catch uint64/int64, or function handles; probably some other
    % stuff too.
    if ( (~isvector(varargin{i}) && ndims(varargin{i}) ~= 2 ) || ...
            iscell(varargin{i}) )
        error('Argument %d is not a vector or 2D matrix!', i)
    end
    % We will error out here if we didn't catch something earlier, as min
    % will die.
    curMin = min(min(varargin{i}));
    if ( i == 1 )
        % First time through is always the min value.
        smallestVal = curMin;
    elseif ( curMin < smallestVal )
        % Compare against current smallest value.
        smallestVal = curMin;
    end
end
end

largestMax

% LARGESTMAX(X) The largest component of all vector and/or matrix inputs.
%   LARGESTMAX(X), where X is any number of vectors and/or matrices, is 
%   the largest component present in any of these structures.  
%   LARGESTMAX(X) is consistent with MATLAB's built-in MAX(X) for vectors
%   only!! LARGESTMAX(X) for a matrix returns the equivalent of 
%   MAX(MAX(X)).  LARGESTMAX accepts mixed inputs of vectors and matrices.
%   A matrix is defined as having exactly 2 dimensions.
%
%   VALID INPUTS: row and column vectors and 2-D matrices.  These can
%                 contain characters, complex numbers, and numerics.
%                 Logicals, doubles, singles, int8, uint8, int16, int32,
%                 and uint32 are also valid.
%   INVALID INPUTS: cell arrays, 64-bit signed and unsigned values,
%   structures, function handles, user classes, and java classes.
%
%   These lists may not be all-inclusive.
%
%   Ex 1: If A = [1 2 3 4] and B = [-1   then largestMax(A,B) returns 5.
%                                    2
%                                    3
%                                    5]
%
%   Ex 2: If A = [1 2   and B = [5 6 7 8] then largestMax(A,B) returns 8.
%                 3 4]
%
%   Author:  Brian A. Schrameck
%   Version: v0.1
%   Date:    07/15/2009
%   Email:   <a href="mailto:schrameckbrian+largestMax@gmail.com">schrameckbrian+largestMax@gmail.com</a>
%
%   See also max, min, isnumeric, and smallestMin (available on the MATLAB
%   File Exchange).

function largestVal = largestMax (varargin)

largestVal = 0;

if ( length(varargin) < 1 )
    error('At least 1 argument must be present!')
end

for i=1:length(varargin)
    % This will catch at least most of the bad stuff thrown at it.  It will
    % not catch uint64/int64, or function handles; probably some other
    % stuff too.
    if ( (~isvector(varargin{i}) && ndims(varargin{i}) ~= 2 ) || ...
            iscell(varargin{i}) )
        error('Argument %d is not a vector or 2D matrix!', i)
    end
    % We will error out here if we didn't catch something earlier, as max
    % will die.
    curMax = max(max(varargin{i}));
    if ( i == 1 )
        % First time through is always the max value.
        largestVal = curMax;
    elseif ( curMax > largestVal )
        % Compare against current largest value.
        largestVal = curMax;
    end
end
end

Aww, how cute.

Look, my first C++ project! Just look at the lines that are 196 characters long and improper indentation. Isn’t it adorable?

//<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>\\
// Brian Schrameck																\\
// COMP 115																		\\
// Started:  September 15, 2004													\\
// Last Modified:  September 26, 2004											\\
//																				\\
// SchrameckProject1.cpp:  A program that takes a user-input dollar amount		\\
// and maximizes the amount of candy able to be bought with it.  It will		\\
// buy as much of the favorite candy as possible, and then buy as much			\\
// of the next favorite, etc., until no more candy is able to be bought.		\\
// It then displays the candy bought, the amount, and the price, as well		\\
// as the total price and the leftover money in a table, with decimal points	\\
// lined-up appropriately.														\\
//																				\\
// I have abided by the Wheaton Honor Code in this work.						\\
// - Brian A. Schrameck															\\
//<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>\\

#include <iomanip>
#include <iostream>
using namespace std;

// Declare the price of the candies as constants in CENTS.

const int TURTLE = 225; 	// Ton o' Chocolate Turtles
const int CUPS = 90; 		// Nutty Cups
const int TRUFFLE = 55; 	// Death by Chocolate Truffles
const int TWISTER = 7; 		// Twisters

int main()
{
double cash; 					// How much money the person has to start out with.
int cents; 					    // How much money the person has in CENTS.
int turtles; 					// How many Ton o' Chocolate Turtles the person will be able to buy.
int cashafterturtle;			// How much money the person has in cents after buying the turtles.
int cups;						// How many Nutty Cups the person will be able to buy.
int cashaftercups;				// How much money the person has in cents after buying the cups.
int truffles;					// How many Death by Chocolate Truffles the person will be able to buy.
int cashaftertruffles;			// How much money the person has in cents after buying the truffles.
int twisters;					// How many Twisters the person will be able to buy.
int cashaftertwisters;			// How much money the person has in cents after buying the twisters.
float turtlescost;				// How much money the turtles cost total in dollars.
float cupscost;					// How much money the cups cost total in dollars.
float trufflescost;				// How much money the truffles cost total in dollars.
float twisterscost;				// How much money the twisters cost total in dollars.
float total; 					// How much money is spent total.
float change;					// How much money is leftover.

// Ask the user how much money they have to spend.

cout << "How much money do you have to spend on the candy?" << endl;
cin >> cash;

// If the user provides 0 as their cash amount, they will be asked to go get some money.

while  (cash == 0)
{
cout << "You can't buy something for nothing!  Get some money, silly!" << endl;
cout << "How much money do you have to spend, now?" << endl;
cin >> cash;
}

// Convert the amount of money from dollars into cents.

cents = 100.0 * cash;

// Find out how many of each candy you will be able to buy, then find out how much change you have leftover after each.

turtles = cents / TURTLE;
cashafterturtle = cents % TURTLE;

cups = cashafterturtle / CUPS;
cashaftercups = cashafterturtle % CUPS;

truffles = cashaftercups / TRUFFLE;
cashaftertruffles = cashaftercups % TRUFFLE;

twisters = cashaftercups / TWISTER;
cashaftertwisters = cashaftercups % TWISTER;

// Find out how much the total amount of each batch of candy cost.

turtlescost = (TURTLE / 100.0) * turtles;
cupscost = (CUPS / 100.0) * cups;
trufflescost = (TRUFFLE / 100.0) * truffles;
twisterscost = (TWISTER / 100.0) * twisters;

// Find out the total cost.

total = turtlescost + cupscost + trufflescost + twisterscost;

// Find out how much money will be leftover.

change = cash - total;

// Display the "receipt".

cout << endl;
cout << "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" << endl;
cout << "|                                                   |" << endl;
cout << "|                Brian's Candy Shop                 |" << endl;
cout << "|                   Your Receipt                    |" << endl;
cout << "|                                                   |" << endl;
cout << "|                                                   |" << endl;
cout << "| Candy                        Amount       Price   |" << endl;
cout << "| ------------------------------------------------- |" << endl;
cout << "| " << setprecision (2) << fixed << "Ton o' Chocolate Turtles" << setw (6) << "| " << setw (4) << turtles << " |" << setw (5) << "$" << setw (6) << turtlescost << "   |" << endl;
cout << "| " << "Nutty Cups" << setw (20) << "| " << setw (4) << cups << " |" << setw (5) << "$" << setw (6) << cupscost << "   |" << endl;
cout << "| " << "Death by Chocolate Truffles" << setw (3) << "| " << setw (4) << truffles << " |" << setw (5) << "$" << setw (6) << trufflescost << "   |" << endl;
cout << "| " << "Twisters" << setw (22) << "| " << setw (4) << twisters << " |" << setw (5) << "$" << setw (6) << twisterscost << "   |" << endl;
cout << "| ------------------------------------------------- |" << endl;
cout << "| TOTAL:" << setw (35) << "$" << setw (6) << total << "   |" << endl;
cout << "| ------------------------------------------------- |" << endl;
cout << "| Leftover:" << setw (32) << "$" << setw (6) << change << "   |" << endl;
cout << "|                                                   |" << endl;
cout << "|                                                   |" << endl;
cout << "|                    Thank you!                     |" << endl;
cout << "|                Please come again!                 |" << endl;
cout << "|                                                   |" << endl;
cout << "|                                                   |" << endl;
cout << "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" << endl;

return 0;
}

Sample output with an input of $50.00

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|                                                   |
|                Brian's Candy Shop                 |
|                   Your Receipt                    |
|                                                   |
|                                                   |
| Candy                        Amount       Price   |
| ------------------------------------------------- |
| Ton o' Chocolate Turtles    |   22 |    $ 49.50   |
| Nutty Cups                  |    0 |    $  0.00   |
| Death by Chocolate Truffles |    0 |    $  0.00   |
| Twisters                    |    7 |    $  0.49   |
| ------------------------------------------------- |
| TOTAL:                                  $ 49.99   |
| ------------------------------------------------- |
| Leftover:                               $  0.01   |
|                                                   |
|                                                   |
|                    Thank you!                     |
|                Please come again!                 |
|                                                   |
|                                                   |
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^