task: metadata: install: pip3 install .[plotting,zurich-instruments,tektronix,Faster-fractions]
patch: diff --git a/qupulse/_program/waveforms.py b/qupulse/_program/waveforms.py
index 701db140..61ae624d 100644
--- a/qupulse/_program/waveforms.py
+++ b/qupulse/_program/waveforms.py
@@ -22,12 +22,19 @@ from qupulse.expressions import ExpressionScalar
 from qupulse.pulses.interpolation import InterpolationStrategy
 from qupulse.utils import checked_int_cast, isclose
 from qupulse.utils.types import TimeType, time_from_float
+from qupulse._program.transformation import Transformation
+from qupulse.utils import pairwise
+
 
 __all__ = ["Waveform", "TableWaveform", "TableWaveformEntry", "FunctionWaveform", "SequenceWaveform",
            "MultiChannelWaveform", "RepetitionWaveform", "TransformingWaveform", "ArithmeticWaveform"]
 
 PULSE_TO_WAVEFORM_ERROR = None  # error margin in pulse template to waveform conversion
 
+#  these are private because there probably will be changes here
+_ALLOCATION_FUNCTION = np.full_like  # pre_allocated = ALLOCATION_FUNCTION(sample_times, **ALLOCATION_FUNCTION_KWARGS)
+_ALLOCATION_FUNCTION_KWARGS = dict(fill_value=np.nan, dtype=float)
+
 
 class Waveform(Comparable, metaclass=ABCMeta):
     """Represents an instantiated PulseTemplate which can be sampled to retrieve arrays of voltage
@@ -224,9 +231,16 @@ class TableWaveform(Waveform):
                       sample_times: np.ndarray,
                       output_array: Union[np.ndarray, None] = None) -> np.ndarray:
         if output_array is None:
-            output_array = np.empty_like(sample_times)
+            output_array = _ALLOCATION_FUNCTION(sample_times, **_ALLOCATION_FUNCTION_KWARGS)
 
-        for entry1, entry2 in zip(self._table[:-1], self._table[1:]):
+        if PULSE_TO_WAVEFORM_ERROR:
+            # we need to replace the last entry's t with self.duration
+            *entries, last = self._table
+            entries.append(TableWaveformEntry(float(self.duration), last.v, last.interp))
+        else:
+            entries = self._table
+
+        for entry1, entry2 in pairwise(entries):
             indices = slice(np.searchsorted(sample_times, entry1.t, 'left'),
                             np.searchsorted(sample_times, entry2.t, 'right'))
             output_array[indices] = \
@@ -247,9 +261,9 @@ class TableWaveform(Waveform):
 
 
 class ConstantWaveform(Waveform):
-    
+
     _is_constant_waveform = True
-    
+
     def __init__(self, duration: float, amplitude: Any, channel: ChannelID):
         """ Create a qupulse waveform corresponding to a ConstantPulseTemplate """
         self._duration = duration
@@ -277,9 +291,10 @@ class ConstantWaveform(Waveform):
                       sample_times: np.ndarray,
                       output_array: Union[np.ndarray, None] = None) -> np.ndarray:
         if output_array is None:
-            output_array = np.empty_like(sample_times, dtype=float)
-        output_array[:] = self._amplitude
-        return output_array
+            return np.full_like(sample_times, fill_value=self._amplitude, dtype=float)
+        else:
+            output_array[:] = self._amplitude
+            return output_array
 
     def unsafe_get_subset_for_channels(self, channels: Set[ChannelID]) -> Waveform:
         """Unsafe version of :func:`~qupulse.pulses.instructions.get_measurement_windows`."""
@@ -325,10 +340,12 @@ class FunctionWaveform(Waveform):
                       channel: ChannelID,
                       sample_times: np.ndarray,
                       output_array: Union[np.ndarray, None] = None) -> np.ndarray:
+        evaluated = self._expression.evaluate_numeric(t=sample_times)
         if output_array is None:
-            output_array = np.empty(len(sample_times))
-        output_array[:] = self._expression.evaluate_numeric(t=sample_times)
-        return output_array
+            return evaluated.astype(float)
+        else:
+            output_array[:] = evaluated
+            return output_array
 
     def unsafe_get_subset_for_channels(self, channels: AbstractSet[ChannelID]) -> Waveform:
         return self
@@ -336,7 +353,6 @@ class FunctionWaveform(Waveform):
 
 class SequenceWaveform(Waveform):
     """This class allows putting multiple PulseTemplate together in one waveform on the hardware."""
-
     def __init__(self, sub_waveforms: Iterable[Waveform]):
         """
 
@@ -371,7 +387,7 @@ class SequenceWaveform(Waveform):
                       sample_times: np.ndarray,
                       output_array: Union[np.ndarray, None] = None) -> np.ndarray:
         if output_array is None:
-            output_array = np.empty_like(sample_times)
+            output_array = _ALLOCATION_FUNCTION(sample_times, **_ALLOCATION_FUNCTION_KWARGS)
         time = 0
         for subwaveform in self._sequenced_waveforms:
             # before you change anything here, make sure to understand the difference between basic and advanced
@@ -536,7 +552,7 @@ class RepetitionWaveform(Waveform):
                       sample_times: np.ndarray,
                       output_array: Union[np.ndarray, None] = None) -> np.ndarray:
         if output_array is None:
-            output_array = np.empty_like(sample_times)
+            output_array = _ALLOCATION_FUNCTION(sample_times, **_ALLOCATION_FUNCTION_KWARGS)
         body_duration = self._body.duration
         time = 0
         for _ in range(self._repetition_count):
diff --git a/qupulse/pulses/plotting.py b/qupulse/pulses/plotting.py
index 75b67eee..fc7646ff 100644
--- a/qupulse/pulses/plotting.py
+++ b/qupulse/pulses/plotting.py
@@ -14,6 +14,7 @@ import warnings
 import operator
 import itertools
 
+from qupulse._program import waveforms
 from qupulse.utils.types import ChannelID, MeasurementWindow, has_type_interface
 from qupulse.pulses.pulse_template import PulseTemplate
 from qupulse.pulses.parameters import Parameter
@@ -86,7 +87,7 @@ def render(program: Union[Loop],
     times = np.linspace(float(start_time), float(end_time), num=int(sample_count), dtype=float)
     times[-1] = np.nextafter(times[-1], times[-2])
 
-    voltages = {ch: np.empty_like(times)
+    voltages = {ch: waveforms._ALLOCATION_FUNCTION(times, **waveforms._ALLOCATION_FUNCTION_KWARGS)
                 for ch in channels}
     for ch, ch_voltage in voltages.items():
         waveform.get_sampled(channel=ch, sample_times=times, output_array=ch_voltage)

problem_statement: TablePT can generate random values
A minimal example is:
```
import matplotlib.pyplot as plt
from qupulse.pulses import TablePT 
from qupulse.pulses.plotting import plot

import qupulse.utils.sympy
import qupulse._program.waveforms

qupulse._program.waveforms.PULSE_TO_WAVEFORM_ERROR=1e-6


d=598.3333333333334-480
c2=TablePT(entries={'P': [(0, 1.0, 'hold'), (d, 1.0, 'hold')]})

plt.figure(2)
plt.clf()
_ = plot(c2, sample_rate=3., axes=plt.gca() )
```
The output is:

![image](https://user-images.githubusercontent.com/17724047/118043232-64e1d780-b375-11eb-96ce-b5745a312e0d.png)

To show this properly in `qupulse.pulses.plotting.render` I changed
```
    voltages = {ch: np.empty_like(times)
                for ch in channels}
```
into
```
    voltages = {ch: np.empty_like(times)*0+1000
                for ch in channels}
```
so that the undefined values from `empty_like` are set to 1000.

The `qupulse._program.waveforms.PULSE_TO_WAVEFORM_ERROR=1e-6` introduces a rounding error where a sample is selected at a time slightly larger then the the duration of the `TablePT`. I could not quickly create a minimal example for the default `qupulse` settings.

@terrorfisch 


repo: qutech/qupulse
test_cmd: pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
test_patch: diff --git a/tests/pulses/bug_tests.py b/tests/pulses/bug_tests.py
index 3f66482e..fc1aead7 100644
--- a/tests/pulses/bug_tests.py
+++ b/tests/pulses/bug_tests.py
@@ -1,4 +1,7 @@
 import unittest
+from unittest import mock
+
+import numpy as np
 
 from qupulse.pulses.table_pulse_template import TablePulseTemplate
 from qupulse.pulses.function_pulse_template import FunctionPulseTemplate
@@ -9,6 +12,8 @@ from qupulse.pulses.mapping_pulse_template import MappingPulseTemplate
 
 from qupulse.pulses.plotting import plot
 
+from qupulse._program._loop import to_waveform
+from qupulse.utils import isclose
 
 class BugTests(unittest.TestCase):
 
@@ -65,4 +70,27 @@ class BugTests(unittest.TestCase):
 
         parameter_values = dict(omega=1.0, a=1.0, t_duration="2*pi")
 
-        _ = plot(both, parameters=parameter_values, sample_rate=100)
\ No newline at end of file
+        _ = plot(both, parameters=parameter_values, sample_rate=100)
+
+    def test_issue_584_uninitialized_table_sample(self):
+        """issue 584"""
+        d = 598.3333333333334 - 480
+        tpt = TablePulseTemplate(entries={'P': [(0, 1.0, 'hold'), (d, 1.0, 'hold')]})
+        with mock.patch('qupulse._program.waveforms.PULSE_TO_WAVEFORM_ERROR', 1e-6):
+            wf = to_waveform(tpt.create_program())
+            self.assertTrue(isclose(d, wf.duration, abs_tol=1e-6))
+
+            start_time = 0.
+            end_time = wf.duration
+            sample_rate = 3.
+
+            sample_count = (end_time - start_time) * sample_rate + 1
+
+            times = np.linspace(float(start_time), float(wf.duration), num=int(sample_count), dtype=float)
+            times[-1] = np.nextafter(times[-1], times[-2])
+
+            out = np.full_like(times, fill_value=np.nan)
+            sampled = wf.get_sampled(channel='P', sample_times=times, output_array=out)
+
+            expected = np.full_like(times, fill_value=1.)
+            np.testing.assert_array_equal(expected, sampled)