pep8speaks: Hello @darienmorrow! Thanks for submitting the PR.

 - In the file [`WrightTools/kit/_calculate.py`](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py), following are the PEP8 issues :

> [Line 21:67](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L21): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 24:1](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L24): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 44:61](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L44): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 45:73](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L45): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 51:1](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L51): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 56:40](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L56): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 60:34](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L60): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 61:17](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L61): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 62:22](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L62): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 64:58](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L64): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 65:30](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L65): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 66:30](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L66): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 67:25](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L67): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 69:78](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L69): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 70:24](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L70): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 71:27](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L71): [E261](https://duckduckgo.com/?q=pep8%20E261) at least two spaces before inline comment
> [Line 76:1](https://github.com/wright-group/WrightTools/blob/4e15e503274465ef90aaa870da00ab2f112e4de1/WrightTools/kit/_calculate.py#L76): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace




ksunden: we may wish to look at `unyt` to simplify the computation here, perhaps.

I'm considering replacing the  custom code in the `units` package with that library, which has some potential positive side effects
ksunden: Also, the pep8 warning still mentioned above are all in the docstring (which is why `black` didn't catch it)

I believe `autopep8` will catch that if you don't feel like tracking those down manually
ksunden: https://github.com/ambv/black/issues/318 
(`black` will NOT handle this case, as there exist cases [perhaps poorly reasoned, but still] where doing so will affect correctness of the program, and `black` commits to not making any such changes)
darienmorrow: @ksunden The reason I am not checking the value is because that requires me to be 100% sure about the correct value of the calculation. I am waiting for someone to check my math before I do that.
darienmorrow: @ksunden The reason I do not check for the value in my test is because checking for the value requires me to be absolutely sure about the result of my calculation. I was hoping for someone to check my math before I specify values.
darienmorrow: @ksunden I now check the value in the test. 
ksunden: Im holding off rereviewing until the math itself is approved, I would like to check thebither two outputs as well in the test, please.
diff --git a/WrightTools/data/_data.py b/WrightTools/data/_data.py
index a48cf49..1124658 100644
--- a/WrightTools/data/_data.py
+++ b/WrightTools/data/_data.py
@@ -625,7 +625,7 @@ class Data(Group):
             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 @@ class Data(Group):
 
         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
index 3af381c..16ef7d2 100644
--- 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 @@ import numpy as np
 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"):

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?
diff --git a/tests/kit/fluence.py b/tests/kit/fluence.py
new file mode 100644
index 0000000..5355d64
--- /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)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov
apt-get update apt-get install -y libfreetype6-dev libopenblas-dev
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
black==25.1.0
cfgv==3.4.0
click==8.1.8
contourpy==1.3.0
coverage==7.8.0
cycler==0.12.1
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.18.0
fonttools==4.56.0
h5py==3.13.0
identify==2.6.9
imageio==2.37.0
importlib_resources==6.5.2
iniconfig==2.1.0
kiwisolver==1.4.7
lazy_loader==0.4
matplotlib==3.9.4
mypy-extensions==1.0.0
networkx==3.2.1
nodeenv==1.9.1
numexpr==2.10.2
numpy==2.0.2
packaging==24.2
pathspec==0.12.1
pillow==11.1.0
platformdirs==4.3.7
pluggy==1.5.0
pre_commit==4.2.0
pydocstyle==6.3.0
pyparsing==3.2.3
pytest==8.3.5
pytest-cov==6.0.0
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.2
scikit-image==0.24.0
scipy==1.13.1
six==1.17.0
snowballstemmer==2.2.0
swebench_matterhorn @ file:///swebench_matterhorn
tidy_headers==1.0.4
tifffile==2024.8.30
tomli==2.2.1
typing_extensions==4.13.0
virtualenv==20.29.3
-e git+https://github.com/wright-group/WrightTools.git@32e4571ed7acb3c1b7588d5857785d9d91d3bd18#egg=WrightTools
zipp==3.21.0

name: WrightTools
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - black==25.1.0
      - cfgv==3.4.0
      - click==8.1.8
      - contourpy==1.3.0
      - coverage==7.8.0
      - cycler==0.12.1
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - fonttools==4.56.0
      - h5py==3.13.0
      - identify==2.6.9
      - imageio==2.37.0
      - importlib-resources==6.5.2
      - iniconfig==2.1.0
      - kiwisolver==1.4.7
      - lazy-loader==0.4
      - matplotlib==3.9.4
      - mypy-extensions==1.0.0
      - networkx==3.2.1
      - nodeenv==1.9.1
      - numexpr==2.10.2
      - numpy==2.0.2
      - packaging==24.2
      - pathspec==0.12.1
      - pillow==11.1.0
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pre-commit==4.2.0
      - pydocstyle==6.3.0
      - pyparsing==3.2.3
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.2
      - scikit-image==0.24.0
      - scipy==1.13.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - swebench-matterhorn==0.0.0
      - tidy-headers==1.0.4
      - tifffile==2024.8.30
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
      - zipp==3.21.0
prefix: /opt/conda/envs/WrightTools

tests/kit/fluence.py::test_0
WrightTools/kit/_calculate.py WrightTools/data/_data.py
WrightTools/kit/_calculate.py WrightTools/data/_data.py