Repository: wright-group/WrightTools

Issue:
Fluence calculation
I oftentimes find myself needing to calculate a fluence from a power reading. I then go through ten minutes of unit conversions to do the calculation. I think we should make a function in `kit._calculate.py` that does this calculation.

For instance:
```
def fluence(power_mW, reprate_Hz, photon_energy_eV, beam_radius_mm):
      
     return uj/cm2, photons/cm2
```

Any thoughts?


Discussion:
@darienmorrow I love this idea, thanks so much

you might consider reparametrizing with `color` and `color_units` (better names possible)
@untzag I think you have a good idea with the color and color_units. I'll include this feature!
of course, with a Gaussian excitation source there are in matter-of-fact a whole range of fluences experienced by different oscillators in the ensemble

you may consider adding a key which allows users to choose between different fluence calculation strategies---for example, two options could be `'peak'` and `'even'`, where even does the normal calculation with the area of a circle and the factor of 2 (better names possible)

alternatively, you could return a dictionary of fluences---this would emphasize to the user that she needs to seriously consider what strategy is best in her situation

Resolving patch:
diff --git a/WrightTools/data/_data.py b/WrightTools/data/_data.py
--- a/WrightTools/data/_data.py
+++ b/WrightTools/data/_data.py
@@ -625,7 +625,7 @@ def downscale(self, tup, name=None, parent=None):
             New data object with the downscaled channels and axes
         """
         if name is None:
-            name = self.natural_name + '_downscaled'
+            name = self.natural_name + "_downscaled"
         if parent is None:
             newdata = Data(name=name)
         else:
@@ -633,13 +633,13 @@ def downscale(self, tup, name=None, parent=None):
 
         for channel in self.channels:
             name = channel.natural_name
-            newdata.create_channel(name=name,
-                                   values=downscale_local_mean(channel[:], tup),
-                                   units=channel.units)
+            newdata.create_channel(
+                name=name, values=downscale_local_mean(channel[:], tup), units=channel.units
+            )
         args = []
         for i, axis in enumerate(self.axes):
             if len(axis.variables) > 1:
-                raise NotImplementedError('downscale only works with simple axes currently')
+                raise NotImplementedError("downscale only works with simple axes currently")
             variable = axis.variables[0]
             name = variable.natural_name
             args.append(name)
diff --git a/WrightTools/kit/_calculate.py b/WrightTools/kit/_calculate.py
--- a/WrightTools/kit/_calculate.py
+++ b/WrightTools/kit/_calculate.py
@@ -1,7 +1,7 @@
 """Calculate."""
 
 
-# --- import --------------------------------------------------------------------------------------
+# --- import -------------------------------------------------------------
 
 
 import numpy as np
@@ -9,13 +9,78 @@
 from .. import units as wt_units
 
 
-# --- define --------------------------------------------------------------------------------------
+# --- define -------------------------------------------------------------
 
 
-__all__ = ["mono_resolution", "nm_width", "symmetric_sqrt"]
+__all__ = ["fluence", "mono_resolution", "nm_width", "symmetric_sqrt"]
 
 
-# --- functions -----------------------------------------------------------------------------------
+# --- functions ----------------------------------------------------------
+
+
+def fluence(
+    power_mW,
+    color,
+    beam_radius,
+    reprate_Hz,
+    pulse_width,
+    color_units="wn",
+    beam_radius_units="mm",
+    pulse_width_units="fs_t",
+    area_type="even",
+):
+    """Calculate the fluence of a beam.
+
+    Parameters
+    ----------
+    power_mW : number
+        Time integrated power of beam.
+    color : number
+        Color of beam in units.
+    beam_radius : number
+        Radius of beam in units.
+    reprate_Hz : number
+        Laser repetition rate in inverse seconds (Hz).
+    pulse_width : number
+        Pulsewidth of laser in units
+    color_units : string (optional)
+        Valid wt.units color unit identifier. Default is wn.
+    beam_radius_units : string (optional)
+        Valid wt.units distance unit identifier. Default is mm.
+    pulse_width_units : number
+        Valid wt.units time unit identifier. Default is fs.
+    area_type : string (optional)
+        Type of calculation to accomplish for Gaussian area.
+        Currently nothing other than the default of even is implemented.
+
+    Returns
+    -------
+    tuple
+        Fluence in uj/cm2, photons/cm2, and peak intensity in GW/cm2
+
+    """
+    # calculate beam area
+    if area_type == "even":
+        radius_cm = wt_units.converter(beam_radius, beam_radius_units, "cm")
+        area_cm2 = np.pi * radius_cm ** 2  # cm^2
+    else:
+        raise NotImplementedError
+    # calculate fluence in uj/cm^2
+    ujcm2 = power_mW / reprate_Hz  # mJ
+    ujcm2 *= 1e3  # uJ
+    ujcm2 /= area_cm2  # uJ/cm^2
+    # calculate fluence in photons/cm^2
+    energy = wt_units.converter(color, color_units, "eV")  # eV
+    photonscm2 = ujcm2 * 1e-6  # J/cm2
+    photonscm2 /= 1.60218e-19  # eV/cm2
+    photonscm2 /= energy  # photons/cm2
+    # calculate peak intensity in GW/cm^2
+    pulse_width_s = wt_units.converter(pulse_width, pulse_width_units, "s_t")  # seconds
+    GWcm2 = ujcm2 / 1e6  # J/cm2
+    GWcm2 /= pulse_width_s  # W/cm2
+    GWcm2 /= 1e9
+    # finish
+    return ujcm2, photonscm2, GWcm2
 
 
 def mono_resolution(grooves_per_mm, slit_width, focal_length, output_color, output_units="wn"):


Test patch:
diff --git a/tests/kit/fluence.py b/tests/kit/fluence.py
new file mode 100644
--- /dev/null
+++ b/tests/kit/fluence.py
@@ -0,0 +1,20 @@
+"""Test fluence."""
+
+
+# --- import -------------------------------------------------------------
+
+
+import numpy as np
+
+import WrightTools as wt
+
+
+# --- test ---------------------------------------------------------------
+
+
+def test_0():
+    out = wt.kit.fluence(1, 2, .1, 1000, 1, "eV", "cm", "ps_t")
+    checks = (31.83098, 99336493460095.2, 0.03183098)
+    assert np.isclose(checks[0], out[0], rtol=1e-3)
+    assert np.isclose(checks[1], out[1], rtol=1e-3)
+    assert np.isclose(checks[2], out[2], rtol=1e-3)