Author: psteitz
Date: Wed Jun  1 20:06:45 2005
New Revision: 179494

URL: http://svn.apache.org/viewcvs?rev=179494&view=rev
Log:
Added RandomAdaptor to complete PRNG pluggability framework, updated User Guide.

Added:
    
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomAdaptor.java
    
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomAdaptorTest.java
Modified:
    jakarta/commons/proper/math/trunk/xdocs/userguide/index.xml
    jakarta/commons/proper/math/trunk/xdocs/userguide/random.xml

Added: 
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomAdaptor.java
URL: 
http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomAdaptor.java?rev=179494&view=auto
==============================================================================
--- 
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomAdaptor.java
 (added)
+++ 
jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomAdaptor.java
 Wed Jun  1 20:06:45 2005
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.random;
+
+import java.util.Random;
+
+/**
+ * Extension of <code>java.util.Random</code> wrapping a
+ * [EMAIL PROTECTED] RandomGenerator}.   
+ *
+ * @since 1.1
+ * @version $Revision:$ $Date$
+ */
+public class RandomAdaptor extends Random implements RandomGenerator {
+    
+    /** Wrapped randomGenerator instance */
+    private RandomGenerator randomGenerator = null;
+    
+    /** 
+     * Prevent instantiation without a generator argument
+     */ 
+    private RandomAdaptor() { }
+    
+    /**
+     * Construct a RandomAdaptor wrapping the supplied RandomGenerator.
+     * 
+     * @param randomGenerator  the wrapped generator
+     */
+    public RandomAdaptor(RandomGenerator randomGenerator) {
+        this.randomGenerator = randomGenerator;
+    } 
+    
+    /**
+     * Factory method to create a <code>Random</code> using the supplied
+     * <code>RandomGenerator</code>.
+     * 
+     * @param randomGenerator
+     * @return a Random instance wrapping the RandomGenerator
+     */
+    public static Random createAdaptor(RandomGenerator randomGenerator) {
+        return new RandomAdaptor(randomGenerator);
+    }
+    
+    /* (non-Javadoc)
+     * @see java.util.Random#nextBoolean()
+     */
+    public boolean nextBoolean() {
+        return randomGenerator.nextBoolean();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#nextBytes(byte[])
+     */
+    public void nextBytes(byte[] bytes) {
+        randomGenerator.nextBytes(bytes);
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#nextDouble()
+     */
+    public double nextDouble() {
+        return randomGenerator.nextDouble();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#nextFloat()
+     */
+    public float nextFloat() {
+        return randomGenerator.nextFloat();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#nextGaussian()
+     */
+    public double nextGaussian() {
+        return randomGenerator.nextGaussian();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#nextInt()
+     */
+    public int nextInt() {
+        return randomGenerator.nextInt();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#nextInt(int)
+     */
+    public int nextInt(int n) {
+        return randomGenerator.nextInt(n);
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#nextLong()
+     */
+    public long nextLong() {
+        return randomGenerator.nextLong();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Random#setSeed(long)
+     */
+    public void setSeed(long seed) {
+        if (randomGenerator != null) {  // required to avoid NPE in constructor
+            randomGenerator.setSeed(seed);
+        }
+    }
+}

Added: 
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomAdaptorTest.java
URL: 
http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomAdaptorTest.java?rev=179494&view=auto
==============================================================================
--- 
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomAdaptorTest.java
 (added)
+++ 
jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomAdaptorTest.java
 Wed Jun  1 20:06:45 2005
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.random;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import java.util.Random;
+
+/**
+ * Test cases for the RandomAdaptor class
+ *
+ * @version $Revision:$ $Date$
+ */
+
+public class RandomAdaptorTest extends RandomDataTest {
+    
+    public RandomAdaptorTest(String name) {
+        super(name);
+    } 
+    
+    public static Test suite() {
+        TestSuite suite = new TestSuite(RandomAdaptorTest.class);
+        suite.setName("RandomAdaptor Tests");
+        return suite;
+    }
+    
+    public void testAdaptor() {
+        ConstantGenerator generator = new ConstantGenerator();
+        Random random = RandomAdaptor.createAdaptor(generator);
+        checkConstant(random);
+        RandomAdaptor randomAdaptor = new RandomAdaptor(generator);
+        checkConstant(randomAdaptor); 
+    }
+    
+    private void checkConstant(Random random) {
+        byte[] bytes = new byte[] {0};
+        random.nextBytes(bytes);
+        assertEquals(0, bytes[0]);  
+        assertEquals(false, random.nextBoolean());
+        assertEquals(0, random.nextDouble(), 0);
+        assertEquals(0, random.nextFloat(), 0);
+        assertEquals(0, random.nextGaussian(), 0);
+        assertEquals(0, random.nextInt());
+        assertEquals(0, random.nextInt(1));
+        assertEquals(0, random.nextLong());
+        random.setSeed(100);
+        assertEquals(0, random.nextDouble(), 0);
+    }
+    
+    /*
+     * "Constant" generator to test Adaptor delegation.
+     * "Powered by Eclipse ;-)"
+     * 
+     */
+    private class ConstantGenerator implements RandomGenerator {
+        
+        public boolean nextBoolean() {
+            return false;
+        }
+        
+        public void nextBytes(byte[] bytes) {
+        }
+
+        public double nextDouble() {
+            return 0;
+        }
+
+        public float nextFloat() {
+            return 0;
+        }
+
+        public double nextGaussian() {
+            return 0;
+        }
+
+        public int nextInt() {
+            return 0;
+        }
+
+        public int nextInt(int n) {
+            return 0;
+        }
+
+        public long nextLong() {
+            return 0;
+        }
+
+        public void setSeed(long seed) {
+        }       
+    }
+}

Modified: jakarta/commons/proper/math/trunk/xdocs/userguide/index.xml
URL: 
http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/xdocs/userguide/index.xml?rev=179494&r1=179493&r2=179494&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/xdocs/userguide/index.xml (original)
+++ jakarta/commons/proper/math/trunk/xdocs/userguide/index.xml Wed Jun  1 
20:06:45 2005
@@ -50,7 +50,8 @@
                 <li><a href="random.html#2.2 Random numbers">2.2 Random 
numbers</a></li>
                 <li><a href="random.html#2.3 Random Strings">2.3 Random 
Strings</a></li>
                 <li><a href="random.html#2.4 Random permutations, 
combinations, sampling">2.4 Random permutations, combinations, sampling</a></li>
-                <li><a href="random.html#2.5 Generating data 
&amp;apos;like&amp;apos; an input file">2.5 Generating data 'like' an input 
file</a></li>
+                <li><a href="random.html#2.5 Generating data 'like' an input 
file">2.5 Generating data 'like' an input file</a></li>
+                <li><a href="random.html#2.6 PRNG Pluggability">2.6 PRNG 
Pluggability</a></li>
                 </ul></li>
             <li><a href="linear.html">3. Linear Algebra</a>
                 <ul>

Modified: jakarta/commons/proper/math/trunk/xdocs/userguide/random.xml
URL: 
http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/xdocs/userguide/random.xml?rev=179494&r1=179493&r2=179494&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/xdocs/userguide/random.xml (original)
+++ jakarta/commons/proper/math/trunk/xdocs/userguide/random.xml Wed Jun  1 
20:06:45 2005
@@ -34,95 +34,130 @@
     <ul>
         <li>generating random numbers</li>
         <li>generating random strings</li>
-        <li>generating cryptographically secure sequences of random numbers or 
strings</li>
+        <li>generating cryptographically secure sequences of random numbers or
+         strings</li>
         <li>generating random samples and permuations</li>
-        <li>analyzing distributions of values in an input file and generating 
values "like"
-            the values in the file</li>
-        <li>generating data for grouped frequency distributions or 
histograms</li>
-    </ul></p>  
+        <li>analyzing distributions of values in an input file and generating
+         values "like" the values in the file</li>
+        <li>generating data for grouped frequency distributions or
+         histograms</li>
+    </ul></p>
+    <p>
+     The source of random data used by the data generation utilities is
+     pluggable.  By default, the JDK-supplied PseudoRandom Number Generator
+     (PRNG) is used, but alternative generators can be "plugged in" using an
+     adaptor framework, which provides a generic facility for replacing 
+     <code>java.util.Random</code> with an alternative PRNG.
+    </p>
+    <p>
+     Sections 2.3-2.5 below show how to use the commons math API to generate
+     different kinds of random data.  The examples all use the default
+     JDK-supplied PRNG.  PRNG pluggability is covered in 2.6.  The only 
+     modification required to the examples to use alternative PRNGs is to
+     replace the argumentless constructor calls with invocations including
+     a <code>RandomGenerator</code> instance as a parameter.
+    </p>  
 </subsection>
 
 <subsection name="2.2 Random numbers" href="deviates">
     <p>
     The <a href="../apidocs/org/apache/commons/math/random/RandomData.html">
-    org.apache.commons.math.RandomData</a> interface defines methods for 
generating
-    random sequences of numbers. The API contracts of these methods use the 
following concepts:
+    org.apache.commons.math.RandomData</a> interface defines methods for
+    generating random sequences of numbers. The API contracts of these methods
+    use the following concepts:
     <dl>
     <dt>Random sequence of numbers from a probability distribution</dt>
-    <dd>There is no such thing as a single "random number."  What can be 
generated
-    are <i>sequences</i> of numbers that appear to be random.  When using the
-    built-in JDK function <code>Math.random(),</code> sequences of values 
generated
-    follow the <a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm";>
-    Uniform Distribution</a>, which means that the values are evenly spread 
over the interval 
-    between 0 and 1, with no sub-interval having a greater probability of 
containing generated 
-    values than any other interval of the same length.  The mathematical 
concept of a <a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda36.htm";>
-    probability distribution</a> basically amounts to asserting that different 
ranges in the set 
-    of possible values for of a random variable have different probabilities 
of containing the value.  
-    Commons Math supports generating random sequences from the following 
probability distributions. The 
-    javadoc for the <code>nextXxx</code> methods in 
<code>RandomDataImpl</code> describes the algorithms used
-    to generate random deviates from each of these distributions. 
+    <dd>There is no such thing as a single "random number."  What can be
+    generated  are <i>sequences</i> of numbers that appear to be random.  When
+    using the built-in JDK function <code>Math.random(),</code> sequences of 
+    values generated follow the 
+    <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm";>
+    Uniform Distribution</a>, which means that the values are evenly spread
+    over the interval  between 0 and 1, with no sub-interval having a greater
+    probability of containing generated values than any other interval of the
+    same length.  The mathematical concept of a
+    <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda36.htm";>
+    probability distribution</a> basically amounts to asserting that different
+    ranges in the set  of possible values for of a random variable have
+    different probabilities of containing the value.  Commons Math supports
+    generating random sequences from the following probability distributions.
+    The javadoc for the <code>nextXxx</code> methods in 
+    <code>RandomDataImpl</code> describes the algorithms used to generate
+    random deviates from each of these distributions. 
     <ul>
-    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm";>uniform 
distribution</a></li>
-    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3667.htm";>exponential
 distribution</a></li>
-    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda366j.htm";>poisson 
distribution</a></li>
-    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3661.htm";>Gaussian
 distribution</a></li>
+    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm";>
+     uniform distribution</a></li>
+    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3667.htm";>
+     exponential distribution</a></li>
+    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda366j.htm";>
+    poisson distribution</a></li>
+    <li><a 
href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3661.htm";>
+    Gaussian distribution</a></li>
     </ul>        
     </dd>
     <dt>Cryptographically secure random sequences</dt>
-    <dd>It is possible for a sequence of numbers to appear random, but 
nonetheless to be
-    predictable based on the algorithm used to generate the sequence. If in 
addition to 
-    randomness, strong unpredictability is required, it is best to use a 
+    <dd>It is possible for a sequence of numbers to appear random, but
+    nonetheless to be predictable based on the algorithm used to generate the
+    sequence. If in addition to randomness, strong unpredictability is
+    required, it is best to use a  
     <a 
href="http://www.wikipedia.org/wiki/Cryptographically_secure_pseudo-random_number_generator";>
-    secure random number generator</a> to generate values (or strings). The 
nextSecureXxx methods
-    in the <code>RandomDataImpl</code> implementation of the 
<code>RandomData</code> interface use the 
-    JDK <code>SecureRandom</code> pseudo-random number generator (PRNG)
-    to generate cryptographically secure sequences.  The 
<code>setSecureAlgorithm</code> method
-    allows you to change the underlying PRNG. These methods are <strong>much 
slower</strong> than
-    the corresponding "non-secure" versions, so they should only be used when 
cryptographic security
-    is required.</dd>
+    secure random number generator</a> to generate values (or strings). The
+    nextSecureXxx methods in the <code>RandomDataImpl</code> implementation of
+    the <code>RandomData</code> interface use the JDK <code>SecureRandom</code>
+    PRNG to generate cryptographically secure sequences.  The 
+    <code>setSecureAlgorithm</code> method allows you to change the underlying
+    PRNG. These methods are <strong>much slower</strong> than the corresponding
+    "non-secure" versions, so they should only be used when cryptographic
+    security is required.</dd>
     <dt>Seeding pseudo-random number generators</dt>
-    <dd>By default, the implementation provided in <code>RandomDataImpl</code> 
uses the JDK-provided
-    PRNG.  Like other PRNGs, the JDK generator generates sequences of random 
numbers based on an initial
-    "seed value".  For the non-secure methods, starting with the same seed 
always produces the same
-    sequence of values.  Secure sequences started with the same seeds will 
diverge. When a new 
-    <code>RandomDataImpl</code> is created, the underlying random number 
generators are 
-    <strong>not</strong> intialized.  The first call to a data generation 
method, or to a 
-    <code>reSeed()</code> method initializes the appropriate generator.  If 
you do not explicitly 
-    seed the generator, it is by default seeded with the current time in 
milliseconds.  Therefore,
-    to generate sequences of random data values, you should always instantiate 
<strong>one</strong>
-    <code>RandomDataImpl</code> and use it repeatedly instead of creating new 
instances for
-    subsequent values in the sequence.  For example, the following will 
generate a random sequence
-    of 50 long integers between 1 and 1,000,000, using the current time in 
milliseconds as the seed
-    for the JDK PRNG:
+    <dd>By default, the implementation provided in <code>RandomDataImpl</code>
+    uses the JDK-provided PRNG.  Like most other PRNGs, the JDK generator
+    generates sequences of random numbers based on an initial "seed value".
+    For the non-secure methods, starting with the same seed always produces the
+    same sequence of values.  Secure sequences started with the same seeds will
+    diverge. When a new <code>RandomDataImpl</code> is created, the underlying
+    random number generators are <strong>not</strong> intialized.  The first
+    call to a data generation method, or to a  <code>reSeed()</code> method
+    initializes the appropriate generator.  If you do not explicitly seed the
+    generator, it is by default seeded with the current time in milliseconds.
+    Therefore, to generate sequences of random data values, you should always
+    instantiate <strong>one</strong> <code>RandomDataImpl</code> and use it
+    repeatedly instead of creating new instances for subsequent values in the
+    sequence.  For example, the following will generate a random sequence of 50
+    long integers between 1 and 1,000,000, using the current time in
+    milliseconds as the seed for the JDK PRNG:
     <source>
-        RandomDataImpl randomData = new RandomDataImpl(); 
-        for (int i = 0; i &lt; 1000; i++) {
-            value = randomData.nextLong(1, 1000000);
-        }
+RandomDataImpl randomData = new RandomDataImpl(); 
+for (int i = 0; i &lt; 1000; i++) {
+    value = randomData.nextLong(1, 1000000);
+}
     </source>
-    The following will not in general produce a good random sequence, since 
the PRNG is reseeded
-    each time through the loop with the current time in milliseconds:
+    The following will not in general produce a good random sequence, since the
+    PRNG is reseeded each time through the loop with the current time in
+    milliseconds:
     <source>
-        for (int i = 0; i &lt; 1000; i++) {
-            RandomDataImpl randomData = new RandomDataImpl(); 
-            value = randomData.nextLong(1, 1000000);
-        }
+for (int i = 0; i &lt; 1000; i++) {
+    RandomDataImpl randomData = new RandomDataImpl(); 
+    value = randomData.nextLong(1, 1000000);
+}
     </source>
-    The following will produce the same random sequence each time it is 
executed:
+    The following will produce the same random sequence each time it is
+    executed:
     <source>
-        RandomDataImpl randomData = new RandomDataImpl(); 
-        randomData.reSeed(1000);
-        for (int i = 0; i = 1000; i++) {
-            value = randomData.nextLong(1, 1000000);
-        }
+RandomDataImpl randomData = new RandomDataImpl(); 
+randomData.reSeed(1000);
+for (int i = 0; i = 1000; i++) {
+    value = randomData.nextLong(1, 1000000);
+}
     </source>
-    The following will produce a different random sequence each time it is 
executed. 
+    The following will produce a different random sequence each time it is
+     executed. 
     <source>
-        RandomDataImpl randomData = new RandomDataImpl(); 
-        randomData.reSeedSecure(1000);
-        for (int i = 0; i &lt; 1000; i++) {
-            value = randomData.nextSecureLong(1, 1000000);
-        }
+RandomDataImpl randomData = new RandomDataImpl(); 
+randomData.reSeedSecure(1000);
+for (int i = 0; i &lt; 1000; i++) {
+    value = randomData.nextSecureLong(1, 1000000);
+}
     </source>
     </dd></dl>
     </p>
@@ -226,6 +261,87 @@
       </dd>
     </dl>
   </p>
+</subsection>
+
+<subsection name="2.6 PRNG Pluggability" href="pluggability">
+  <p>
+      To enable alternative PRNGs to be "plugged in" to the commons-math data
+      generation utilities and to provide a generic means to replace 
+      <code>java.util.Random</code> in applications, a random generator 
+      adaptor framework has been added to commons-math.  The
+      <a href="../apidocs/org/apache/commons/math/random/RandomGenerator.html">
+      org.apache.commons.math.RandomGenerator</a> interface abstracts the 
public
+      interface of <code>java.util.Random</code> and any implementation of this
+      interface can be used as the source of random data for the commons-math 
+      data generation classes.  An abstract superclass, 
+      <a 
href="../apidocs/org/apache/commons/math/random/AbstractRandomGenerator.html">
+      org.apache.commons.math.AbstractRandomGenerator</a> is provided to make
+      implementation easier.  This class provides default implementations of
+      "derived" data generation methods based on the primitive, 
+      <code>nextDouble().</code>  To support generic replacement of 
+      <code>java.util.Random</code>,  the 
+      <a href="../apidocs/org/apache/commons/math/random/RandomAdaptor.html">
+      org.apache.commons.math.RandomAdaptor</a> class is provided,  which
+      extends <code>java.util.Random</code> and wraps and delegates calls to
+      a <code>RandomGenerator</code> instance.   
+  </p>
+  <p>
+     Examples:
+     <dl>
+      <dt>Create a RandomGenerator based on RngPack's Mersenne Twister</dt>
+      <dd>To create a RandomGenerator using the RngPack Mersenne Twister PRNG
+       as the source of randomness, extend <code>AbstractRandomGenerator</code>
+       overriding the derived methods that the RngPack implementation provides:
+       <source>
+import edu.cornell.lassp.houle.RngPack.RanMT;
+/**
+ * AbstractRandomGenerator based on RngPack RanMT generator.
+ */
+public class RngPackGenerator extends AbstractRandomGenerator {
+    
+    private RanMT random = new RanMT();
+    
+    public void setSeed(long seed) {
+       random = new RanMT(seed);
+    }
+    
+    public double nextDouble() {
+        return random.raw();
+    }
+    
+    public double nextGaussian() {
+        return random.gaussian();
+    }
+    
+    public int nextInt(int n) {
+        return random.choose(n);
+    }
+    
+    public boolean nextBoolean() {
+        return random.coin();
+    }
+}
+      </source>
+      </dd>
+      <dt>Use the Mersenne Twister RandomGenerator in place of 
+      <code>java.util.Random</code> in <code>RandomData</code></dt>
+      <dd>
+      <source>
+RandomData randomData = new RandomDataImpl(new RngPackGenerator());
+      </source>
+      </dd>
+      <dt>Create an adaptor instance based on the Mersenne Twister generator
+      that can be used in place of a <code>Random</code></dt>
+      <dd>
+      <source>
+ RandomGenerator generator = new RngPackGenerator();
+ Random random = RandomAdaptor.createAdaptor(generator);
+ // random can now be used in place of a Random instance, data generation
+ // calls will be delegated to the wrapped Mersenne Twister
+      </source>
+      </dd>
+  </dl>
+ </p>
 </subsection>
 
 </section>



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to