diff --git a/src/uproot/_util.py b/src/uproot/_util.py index a3b8c805e..f9e921508 100644 --- a/src/uproot/_util.py +++ b/src/uproot/_util.py @@ -42,6 +42,17 @@ range = eval("range") +def tobytes(array): + """ + Calls ``array.tobytes()`` or its older equivalent, ``array.tostring()``, + depending on what's available in this NumPy version. + """ + if hasattr(array, "tobytes"): + return array.tobytes() + else: + return array.tostring() + + def isint(x): """ Returns True if and only if ``x`` is an integer (including NumPy, not diff --git a/src/uproot/_writing.py b/src/uproot/_writing.py index 902e76f30..74bb47449 100644 --- a/src/uproot/_writing.py +++ b/src/uproot/_writing.py @@ -967,7 +967,7 @@ def deserialize(cls, raw_bytes, location, key, freesegments, file_path, uuid): rawstreamers.append( RawStreamerInfo( location + start, - uncompressed.raw_data[start:stop].tobytes(), + uproot._util.tobytes(uncompressed.raw_data[start:stop]), streamer.name, streamer.class_version, ) @@ -2185,3 +2185,18 @@ def update_existing(sink, initial_directory_bytes, uuid_function): return CascadingFile( fileheader, streamers, freesegments, rootdirectory, tlist_of_streamers ) + + +_serialize_string_small = struct.Struct(">B") +_serialize_string_big = struct.Struct(">BI") + + +def serialize_string(string): + """ + FIXME: docstring + """ + as_bytes = string.encode(errors="surrogateescape") + if len(as_bytes) < 255: + return _serialize_string_small.pack(len(as_bytes)) + as_bytes + else: + return _serialize_string_big.pack(255, len(as_bytes)) + as_bytes diff --git a/src/uproot/const.py b/src/uproot/const.py index f9790a2b5..a202e3d67 100644 --- a/src/uproot/const.py +++ b/src/uproot/const.py @@ -19,6 +19,7 @@ kIsOnHeap = numpy.uint32(0x01000000) kNotDeleted = numpy.uint32(0x02000000) +kMustCleanup = numpy.uint32(1 << 3) kIsReferenced = numpy.uint32(1 << 4) kMapOffset = 2 diff --git a/src/uproot/interpretation/strings.py b/src/uproot/interpretation/strings.py index fc7207e31..64524fc81 100644 --- a/src/uproot/interpretation/strings.py +++ b/src/uproot/interpretation/strings.py @@ -227,10 +227,7 @@ def basket_array( offsets[0] = 0 numpy.cumsum(counts, out=offsets[1:]) - if hasattr(data, "tobytes"): - data = data.tobytes() - else: - data = data.tostring() + data = uproot._util.tobytes(data) output = StringArray(offsets, data) self.hook_after_basket_array( diff --git a/src/uproot/model.py b/src/uproot/model.py index 70e29d0c9..679b6f096 100644 --- a/src/uproot/model.py +++ b/src/uproot/model.py @@ -91,11 +91,14 @@ def reset_classes(): reload(uproot.models.TObjString) reload(uproot.models.TAtt) reload(uproot.models.TRef) + reload(uproot.models.TTree) reload(uproot.models.TBranch) reload(uproot.models.TLeaf) reload(uproot.models.TBasket) reload(uproot.models.RNTuple) + reload(uproot.models.TH) + reload(uproot.models.TGraph) _classname_regularize = re.compile(r"\s*(<|>|::)\s*") @@ -361,6 +364,7 @@ class Model(object): class_streamer = None class_rawstreamers = () writable = False + _deeply_writable = False behaviors = () def __repr__(self): @@ -721,6 +725,7 @@ def empty(cls): self._cursor = None self._file = None self._parent = None + self._concrete = None self._members = {} self._bases = [] self._num_bytes = None @@ -757,7 +762,6 @@ def read(cls, chunk, cursor, context, file, selffile, parent, concrete=None): self._file = selffile self._parent = parent self._concrete = concrete - self._members = {} self._bases = [] self._num_bytes = None @@ -956,9 +960,71 @@ def hook_before_postprocess(self, **kwargs): """ pass - def _serialize(self, out, header, name): + def _to_writable_postprocess(self, original): + pass + + def _to_writable(self, concrete): + cls = None + if self.writable: + cls = type(self) + else: + dispatch = uproot.classes.get(self.classname) + if dispatch is not None: + for versioned_cls in dispatch.known_versions.values(): + if versioned_cls.writable: + cls = versioned_cls + break + + if cls is None: + raise NotImplementedError( + "this ROOT type is not writable: {0}".format(self.classname) + ) + else: + out = cls.__new__(cls) + out._cursor = self._cursor + out._file = self._file + out._parent = self._parent + out._concrete = concrete + out._num_bytes = self._num_bytes + out._instance_version = classname_decode(cls.__name__)[1] + out._is_memberwise = self._is_memberwise + + if concrete is None: + concrete = out + + out._bases = [] + for base in self._bases: + out._bases.append(base._to_writable(concrete)) + + out._members = {} + for key, value in self._members.items(): + if isinstance(value, Model): + out._members[key] = value._to_writable(None) + else: + out._members[key] = value + + out._to_writable_postprocess(self) + return out + + def to_writable(self): + """ + Args: + obj (:doc:`uproot.model.Model` instance of the same C++ class): The + object to convert to this class version. + + Returns a writable version of this object or raises a NotImplementedError + if no writable version exists. + """ + if self._deeply_writable: + return self + else: + return self._to_writable(None) + + def _serialize(self, out, header, name, tobject_flags): raise NotImplementedError( - "serialize method not implemented on {0}".format(type(self).__name__) + "can't write {0} instances yet ('serialize' method not implemented)".format( + type(self).__name__ + ) ) def serialize(self, name=None): @@ -972,7 +1038,7 @@ def serialize(self, name=None): NotImplementedError). """ out = [] - self._serialize(out, True, name) + self._serialize(out, True, name, numpy.uint32(0x00000000)) return b"".join(out) diff --git a/src/uproot/models/TArray.py b/src/uproot/models/TArray.py index 290ca7a70..88d61be2b 100644 --- a/src/uproot/models/TArray.py +++ b/src/uproot/models/TArray.py @@ -91,6 +91,24 @@ def awkward_form( parameters={"uproot": {"as": "TArray"}}, ) + writable = True + + def _to_writable_postprocess(self, original): + self._data = original._data + + def _serialize(self, out, header, name, tobject_flags): + where = len(out) + for x in self._bases: + x._serialize(out, True, None, tobject_flags) + + out.append(_tarray_format1.pack(self._members["fN"])) + out.append(uproot._util.tobytes(self._data)) + + if header: + num_bytes = sum(len(x) for x in out[where:]) + version = 1 + out.insert(where, uproot.serialization.numbytes_version(num_bytes, version)) + class Model_TArrayC(Model_TArray): """ diff --git a/src/uproot/models/TAtt.py b/src/uproot/models/TAtt.py index 4d6ae1059..e73d0ff19 100644 --- a/src/uproot/models/TAtt.py +++ b/src/uproot/models/TAtt.py @@ -157,6 +157,24 @@ def awkward_form( ) return awkward.forms.RecordForm(contents, parameters={"__record__": "TAttLine"}) + writable = True + + def _serialize(self, out, header, name, tobject_flags): + where = len(out) + for x in self._bases: + x._serialize(out, True, None, tobject_flags) + out.append( + _tattline1_format1.pack( + self._members["fLineColor"], + self._members["fLineStyle"], + self._members["fLineWidth"], + ) + ) + if header: + num_bytes = sum(len(x) for x in out[where:]) + version = 2 + out.insert(where, uproot.serialization.numbytes_version(num_bytes, version)) + base_names_versions = [] member_names = ["fLineColor", "fLineStyle", "fLineWidth"] class_flags = {} @@ -305,6 +323,23 @@ def awkward_form( ) return awkward.forms.RecordForm(contents, parameters={"__record__": "TAttFill"}) + writable = True + + def _serialize(self, out, header, name, tobject_flags): + where = len(out) + for x in self._bases: + x._serialize(out, True, None, tobject_flags) + out.append( + _tattfill2_format1.pack( + self._members["fFillColor"], + self._members["fFillStyle"], + ) + ) + if header: + num_bytes = sum(len(x) for x in out[where:]) + version = 2 + out.insert(where, uproot.serialization.numbytes_version(num_bytes, version)) + base_names_versions = [] member_names = ["fFillColor", "fFillStyle"] class_flags = {} @@ -392,6 +427,24 @@ def awkward_form( contents, parameters={"__record__": "TAttMarker"} ) + writable = True + + def _serialize(self, out, header, name, tobject_flags): + where = len(out) + for x in self._bases: + x._serialize(out, True, None, tobject_flags) + out.append( + _tattmarker2_format1.pack( + self._members["fMarkerColor"], + self._members["fMarkerStyle"], + self._members["fMarkerSize"], + ) + ) + if header: + num_bytes = sum(len(x) for x in out[where:]) + version = 2 + out.insert(where, uproot.serialization.numbytes_version(num_bytes, version)) + base_names_versions = [] member_names = ["fMarkerColor", "fMarkerStyle", "fMarkserSize"] class_flags = {} @@ -414,7 +467,8 @@ class Model_TAttAxis_v4(uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -600,11 +654,25 @@ def awkward_form( writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, None, tobject_flags) + out.append( + self._format0.pack( + self._members["fNdivisions"], + self._members["fAxisColor"], + self._members["fLabelColor"], + self._members["fLabelFont"], + self._members["fLabelOffset"], + self._members["fLabelSize"], + self._members["fTickLength"], + self._members["fTitleOffset"], + self._members["fTitleSize"], + self._members["fTitleColor"], + self._members["fTitleFont"], + ) + ) if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -627,7 +695,8 @@ class Model_TAtt3D_v1(uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -689,11 +758,10 @@ def awkward_form( writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, None, tobject_flags) if header: num_bytes = sum(len(x) for x in out[where:]) version = 1 diff --git a/src/uproot/models/TBranch.py b/src/uproot/models/TBranch.py index 9c2d0ad58..589a96690 100644 --- a/src/uproot/models/TBranch.py +++ b/src/uproot/models/TBranch.py @@ -26,6 +26,8 @@ class Model_TBranch_v10(uproot.behaviors.TBranch.TBranch, uproot.model.Versioned A :doc:`uproot.model.VersionedModel` for ``TBranch`` version 10. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -154,6 +156,8 @@ class Model_TBranch_v11(uproot.behaviors.TBranch.TBranch, uproot.model.Versioned A :doc:`uproot.model.VersionedModel` for ``TBranch`` version 11. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -284,6 +288,8 @@ class Model_TBranch_v12(uproot.behaviors.TBranch.TBranch, uproot.model.Versioned A :doc:`uproot.model.VersionedModel` for ``TBranch`` version 12. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -415,6 +421,8 @@ class Model_TBranch_v13(uproot.behaviors.TBranch.TBranch, uproot.model.Versioned A :doc:`uproot.model.VersionedModel` for ``TBranch`` version 13. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -563,6 +571,8 @@ class Model_TBranchElement_v8( A :doc:`uproot.model.VersionedModel` for ``TBranchElement`` version 8. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -635,6 +645,8 @@ class Model_TBranchElement_v9( A :doc:`uproot.model.VersionedModel` for ``TBranchElement`` version 9. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -707,6 +719,8 @@ class Model_TBranchElement_v10( A :doc:`uproot.model.VersionedModel` for ``TBranchElement`` version 10. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -788,6 +802,8 @@ class Model_TBranchObject_v1( A :doc:`uproot.model.VersionedModel` for ``TBranchObject`` version 1. """ + behaviors = (uproot.behaviors.TBranch.TBranch,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( diff --git a/src/uproot/models/TGraph.py b/src/uproot/models/TGraph.py index b6d3c2e91..cf63710c6 100644 --- a/src/uproot/models/TGraph.py +++ b/src/uproot/models/TGraph.py @@ -34,6 +34,8 @@ class Model_TGraph_v4(uproot.behaviors.TGraph.TGraph, uproot.model.VersionedMode A :doc:`uproot.model.VersionedModel` for ``TGraph`` version 4. """ + behaviors = (uproot.behaviors.TGraph.TGraph,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -361,10 +363,10 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) + x._serialize(out, True, name, tobject_flags) raise NotImplementedError("FIXME") if header: num_bytes = sum(len(x) for x in out[where:]) @@ -387,6 +389,8 @@ class Model_TGraphErrors_v3( A :doc:`uproot.model.VersionedModel` for ``TGraphErrors`` version 3. """ + behaviors = (uproot.behaviors.TGraphErrors.TGraphErrors,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -559,10 +563,10 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) + x._serialize(out, True, name, tobject_flags) raise NotImplementedError("FIXME") if header: num_bytes = sum(len(x) for x in out[where:]) @@ -585,6 +589,8 @@ class Model_TGraphAsymmErrors_v3( A :doc:`uproot.model.VersionedModel` for ``TGraphAsymmErrors`` version 3. """ + behaviors = (uproot.behaviors.TGraphAsymmErrors.TGraphAsymmErrors,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -809,10 +815,10 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) + x._serialize(out, True, name, tobject_flags) raise NotImplementedError("FIXME") if header: num_bytes = sum(len(x) for x in out[where:]) diff --git a/src/uproot/models/TH.py b/src/uproot/models/TH.py index 7ae7847b9..e47b39886 100644 --- a/src/uproot/models/TH.py +++ b/src/uproot/models/TH.py @@ -154,7 +154,8 @@ class Model_TAxis_v10(uproot.behaviors.TAxis.TAxis, uproot.model.VersionedModel) def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -430,11 +431,37 @@ def awkward_form( writable = True - def _serialize(self, out, header, name): + def _to_writable_postprocess(self, original): + if "fModLabs" not in self._members: + self._members["fModLabs"] = None + + def _serialize(self, out, header, name, tobject_flags): + import uproot.serialization + where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, None, tobject_flags) + + out.append( + self._format0.pack( + self._members["fNbins"], + self._members["fXmin"], + self._members["fXmax"], + ) + ) + self._members["fXbins"]._serialize(out, False, None, tobject_flags) + out.append( + self._format1.pack( + self._members["fFirst"], + self._members["fLast"], + self._members["fBits2"], + self._members["fTimeDisplay"], + ) + ) + self._members["fTimeFormat"]._serialize(out, False, None, tobject_flags) + uproot.serialization._serialize_object_any(out, self._members["fLabels"], None) + uproot.serialization._serialize_object_any(out, self._members["fModLabs"], None) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 10 @@ -457,7 +484,8 @@ class Model_TH1_v8(uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -540,9 +568,11 @@ def read_members(self, chunk, cursor, context, file): chunk, cursor, context, file, self._file, self.concrete ) self._members["fBufferSize"] = cursor.field(chunk, self._format2, context) - tmp = self._dtype0 if context.get("speedbump", True): - cursor.skip(1) + self._speedbump1 = cursor.byte(chunk, context) + else: + self._speedbump1 = None + tmp = self._dtype0 self._members["fBuffer"] = cursor.array( chunk, self.member("fBufferSize"), tmp, context ) @@ -978,11 +1008,59 @@ def awkward_form( writable = True - def _serialize(self, out, header, name): + def _to_writable_postprocess(self, original): + self._speedbump1 = getattr(original, "_speedbump1", b"\x00") + if "fStatOverflows" not in self._members: + self._members["fStatOverflows"] = 0 + + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, name, tobject_flags | uproot.const.kMustCleanup) + + out.append(self._format0.pack(self._members["fNcells"])) + self._members["fXaxis"]._serialize(out, header, None, tobject_flags) + self._members["fYaxis"]._serialize(out, header, None, tobject_flags) + self._members["fZaxis"]._serialize(out, header, None, tobject_flags) + out.append( + self._format1.pack( + self._members["fBarOffset"], + self._members["fBarWidth"], + self._members["fEntries"], + self._members["fTsumw"], + self._members["fTsumw2"], + self._members["fTsumwx"], + self._members["fTsumwx2"], + self._members["fMaximum"], + self._members["fMinimum"], + self._members["fNormFactor"], + ) + ) + self._members["fContour"]._serialize(out, False, None, tobject_flags) + self._members["fSumw2"]._serialize(out, False, None, tobject_flags) + self._members["fOption"]._serialize(out, False, None, tobject_flags) + self._members["fFunctions"]._serialize( + out, + header, + None, + ( + tobject_flags + | uproot.const.kIsOnHeap + | uproot.const.kNotDeleted + | (1 << 16) # I don't know what this is + ), + ) + out.append(self._format2.pack(self._members["fBufferSize"])) + if self._speedbump1 is not None: + out.append(self._speedbump1) + out.append(uproot._util.tobytes(self._members["fBuffer"])) + out.append( + self._format3.pack( + self._members["fBinStatErrOpt"], + self._members["fStatOverflows"], + ) + ) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 8 @@ -1005,7 +1083,8 @@ class Model_TH2_v5(uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -1142,11 +1221,20 @@ def awkward_form( writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) + for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, name, tobject_flags) + out.append( + self._format0.pack( + self._members["fScalefactor"], + self._members["fTsumwy"], + self._members["fTsumwy2"], + self._members["fTsumwxy"], + ) + ) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 5 @@ -1169,7 +1257,8 @@ class Model_TH3_v6(uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -1377,11 +1466,22 @@ def awkward_form( writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, name, tobject_flags) + out.append( + self._format0.pack( + self._members["fTsumwy"], + self._members["fTsumwy2"], + self._members["fTsumwxy"], + self._members["fTsumwz"], + self._members["fTsumwz2"], + self._members["fTsumwxz"], + self._members["fTsumwyz"], + ) + ) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 6 @@ -1404,7 +1504,8 @@ class Model_TH1C_v3(uproot.behaviors.TH1.TH1, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -1552,11 +1653,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 3 @@ -1579,7 +1680,8 @@ class Model_TH1D_v3(uproot.behaviors.TH1.TH1, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -1722,11 +1824,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 3 @@ -1749,7 +1851,8 @@ class Model_TH1F_v3(uproot.behaviors.TH1.TH1, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -1892,11 +1995,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 3 @@ -1919,7 +2022,8 @@ class Model_TH1I_v3(uproot.behaviors.TH1.TH1, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -2067,11 +2171,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 3 @@ -2094,7 +2198,8 @@ class Model_TH1S_v3(uproot.behaviors.TH1.TH1, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -2242,11 +2347,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 3 @@ -2269,7 +2374,8 @@ class Model_TH2C_v4(uproot.behaviors.TH2.TH2, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -2418,11 +2524,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -2445,7 +2551,8 @@ class Model_TH2D_v4(uproot.behaviors.TH2.TH2, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -2589,11 +2696,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -2616,7 +2723,8 @@ class Model_TH2F_v4(uproot.behaviors.TH2.TH2, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -2765,11 +2873,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -2792,7 +2900,8 @@ class Model_TH2I_v4(uproot.behaviors.TH2.TH2, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -2941,11 +3050,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -2968,7 +3077,8 @@ class Model_TH2S_v4(uproot.behaviors.TH2.TH2, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -3117,11 +3227,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -3144,7 +3254,8 @@ class Model_TH3C_v4(uproot.behaviors.TH3.TH3, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -3294,11 +3405,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -3321,7 +3432,8 @@ class Model_TH3D_v4(uproot.behaviors.TH3.TH3, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -3466,11 +3578,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -3493,7 +3605,8 @@ class Model_TH3F_v4(uproot.behaviors.TH3.TH3, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -3643,11 +3756,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -3670,7 +3783,8 @@ class Model_TH3I_v4(uproot.behaviors.TH3.TH3, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -3820,11 +3934,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -3847,7 +3961,8 @@ class Model_TH3S_v4(uproot.behaviors.TH3.TH3, uproot.model.VersionedModel): def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -3997,11 +4112,11 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) - for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + self._bases[0]._serialize(out, True, name, tobject_flags) + self._bases[1]._serialize(out, False, name, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 4 @@ -4026,7 +4141,8 @@ class Model_TProfile_v7( def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -4239,11 +4355,22 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, name, tobject_flags) + self._members["fBinEntries"]._serialize(out, False, None, tobject_flags) + out.append( + self._format0.pack( + self._members["fErrorMode"], + self._members["fYmin"], + self._members["fYmax"], + self._members["fTsumwy"], + self._members["fTsumwy2"], + ) + ) + self._members["fBinSumw2"]._serialize(out, False, None, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 7 @@ -4268,7 +4395,8 @@ class Model_TProfile2D_v8( def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -4482,11 +4610,23 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, name, tobject_flags) + + self._members["fBinEntries"]._serialize(out, False, None, tobject_flags) + out.append( + self._format0.pack( + self._members["fErrorMode"], + self._members["fZmin"], + self._members["fZmax"], + self._members["fTsumwz"], + self._members["fTsumwz2"], + ) + ) + self._members["fBinSumw2"]._serialize(out, False, None, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 8 @@ -4511,7 +4651,8 @@ class Model_TProfile3D_v8( def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( - "memberwise serialization of {0}\nin file {1}".format( + """memberwise serialization of {0} +in file {1}""".format( type(self).__name__, self.file.file_path ) ) @@ -4726,11 +4867,23 @@ def awkward_form( ) writable = True - def _serialize(self, out, header, name): + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) - raise NotImplementedError("FIXME") + x._serialize(out, True, name, tobject_flags) + + self._members["fBinEntries"]._serialize(out, False, None, tobject_flags) + out.append( + self._format0.pack( + self._members["fErrorMode"], + self._members["fTmin"], + self._members["fTmax"], + self._members["fTsumwt"], + self._members["fTsumwt2"], + ) + ) + self._members["fBinSumw2"]._serialize(out, False, None, tobject_flags) + if header: num_bytes = sum(len(x) for x in out[where:]) version = 8 diff --git a/src/uproot/models/TList.py b/src/uproot/models/TList.py index f553c2206..466bf3f3c 100644 --- a/src/uproot/models/TList.py +++ b/src/uproot/models/TList.py @@ -46,19 +46,17 @@ def read_members(self, chunk, cursor, context, file): self._members["fName"] = cursor.string(chunk, context) self._members["fSize"] = cursor.field(chunk, _tlist_format1, context) - self._data = [] self._starts = [] + self._data = [] + self._options = [] self._stops = [] for _ in uproot._util.range(self._members["fSize"]): self._starts.append(cursor.index) - item = uproot.deserialization.read_object_any( chunk, cursor, context, file, self._file, self._parent ) self._data.append(item) - - cursor.bytestring(chunk, context) # read past and ignore "option" - + self._options.append(cursor.bytestring(chunk, context)) self._stops.append(cursor.index) def __repr__(self): @@ -91,5 +89,30 @@ def tojson(self): "opt": [], } + writable = True + + def _to_writable_postprocess(self, original): + self._data = original._data + self._options = original._options + + def _serialize(self, out, header, name, tobject_flags): + import uproot._writing + + where = len(out) + for x in self._bases: + x._serialize(out, True, None, tobject_flags) + + out.append(uproot._writing.serialize_string(self._members["fName"])) + out.append(_tlist_format1.pack(self._members["fSize"])) + + for datum, option in zip(self._data, self._options): + uproot.serialization._serialize_object_any(out, datum, None) + out.append(option) + + if header: + num_bytes = sum(len(x) for x in out[where:]) + version = 5 + out.insert(where, uproot.serialization.numbytes_version(num_bytes, version)) + uproot.classes["TList"] = Model_TList diff --git a/src/uproot/models/TNamed.py b/src/uproot/models/TNamed.py index 81cc79ae2..1df0bbdd3 100644 --- a/src/uproot/models/TNamed.py +++ b/src/uproot/models/TNamed.py @@ -81,5 +81,27 @@ def awkward_form( parameters={"__record__": "TNamed"}, ) + writable = True + + def _serialize(self, out, header, name, tobject_flags): + import uproot._writing + + where = len(out) + self._bases[0]._serialize( + out, + True, + name, + tobject_flags | uproot.const.kIsOnHeap | uproot.const.kNotDeleted, + ) + if name is None: + out.append(uproot._writing.serialize_string(self._members["fName"])) + else: + out.append(uproot._writing.serialize_string(name)) + out.append(uproot._writing.serialize_string(self._members["fTitle"])) + if header: + num_bytes = sum(len(x) for x in out[where:]) + version = 1 + out.insert(where, uproot.serialization.numbytes_version(num_bytes, version)) + uproot.classes["TNamed"] = Model_TNamed diff --git a/src/uproot/models/TObjArray.py b/src/uproot/models/TObjArray.py index eee94c7fa..8259c8919 100644 --- a/src/uproot/models/TObjArray.py +++ b/src/uproot/models/TObjArray.py @@ -57,10 +57,12 @@ def read_members(self, chunk, cursor, context, file): ) self._data.append(item) - def _serialize(self, out, header, name): + writable = True + + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) + x._serialize(out, True, None, tobject_flags) out.append(uproot.serialization.string(self._members["fName"])) out.append( _tobjarray_format1.pack( @@ -68,7 +70,7 @@ def _serialize(self, out, header, name): ) ) for item in self._data: - uproot.serialization._serialize_object_any(out, item, name) + uproot.serialization._serialize_object_any(out, item, None) if header: out.insert( where, diff --git a/src/uproot/models/TObjString.py b/src/uproot/models/TObjString.py index fd9b6b863..4d48fd378 100644 --- a/src/uproot/models/TObjString.py +++ b/src/uproot/models/TObjString.py @@ -63,10 +63,12 @@ def postprocess(self, chunk, cursor, context, file): def fTitle(self): return "Collectable string class" - def _serialize(self, out, header, name): + writable = True + + def _serialize(self, out, header, name, tobject_flags): where = len(out) for x in self._bases: - x._serialize(out, True, name) + x._serialize(out, True, name, tobject_flags | uproot.const.kNotDeleted) out.append(uproot.serialization.string(str(self))) if header: num_bytes = sum(len(x) for x in out[where:]) diff --git a/src/uproot/models/TObject.py b/src/uproot/models/TObject.py index 97bf79b83..37a6553d9 100644 --- a/src/uproot/models/TObject.py +++ b/src/uproot/models/TObject.py @@ -45,9 +45,10 @@ def read_members(self, chunk, cursor, context, file): cursor.skip(2) self._members["@fBits"] = int(self._members["@fBits"]) - def _serialize(self, out, header, name): - # struct.pack(">hII", 1, 0, uproot.const.kNotDeleted) - out.append(b"\x00\x01\x00\x00\x00\x00\x02\x00\x00\x00") + writable = True + + def _serialize(self, out, header, name, tobject_flags): + out.append(b"\x00\x01" + _tobject_format2.pack(0, tobject_flags)) @classmethod def strided_interpretation( diff --git a/src/uproot/models/TString.py b/src/uproot/models/TString.py index c6f1a05cc..c74547095 100644 --- a/src/uproot/models/TString.py +++ b/src/uproot/models/TString.py @@ -60,5 +60,22 @@ def awkward_form( file, index_format, header, tobject_header, breadcrumbs ) + writable = True + _is_memberwise = False + + def _serialize(self, out, header, name, tobject_flags): + import uproot._writing + + where = len(out) + for x in self._bases: + x._serialize(out, True, None, tobject_flags) + + out.append(uproot._writing.serialize_string(self)) + + if header: + num_bytes = sum(len(x) for x in out[where:]) + version = 2 + out.insert(where, uproot.serialization.numbytes_version(num_bytes, version)) + uproot.classes["TString"] = Model_TString diff --git a/src/uproot/models/TTree.py b/src/uproot/models/TTree.py index 5ebadefdf..eb1549d42 100644 --- a/src/uproot/models/TTree.py +++ b/src/uproot/models/TTree.py @@ -24,6 +24,8 @@ class Model_TTree_v16(uproot.behaviors.TTree.TTree, uproot.model.VersionedModel) A :doc:`uproot.model.VersionedModel` for ``TTree`` version 16. """ + behaviors = (uproot.behaviors.TTree.TTree,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -174,6 +176,8 @@ class Model_TTree_v17(uproot.behaviors.TTree.TTree, uproot.model.VersionedModel) A :doc:`uproot.model.VersionedModel` for ``TTree`` version 17. """ + behaviors = (uproot.behaviors.TTree.TTree,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -325,6 +329,8 @@ class Model_TTree_v18(uproot.behaviors.TTree.TTree, uproot.model.VersionedModel) A :doc:`uproot.model.VersionedModel` for ``TTree`` version 18. """ + behaviors = (uproot.behaviors.TTree.TTree,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -482,6 +488,8 @@ class Model_TTree_v19(uproot.behaviors.TTree.TTree, uproot.model.VersionedModel) A :doc:`uproot.model.VersionedModel` for ``TTree`` version 19. """ + behaviors = (uproot.behaviors.TTree.TTree,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( @@ -655,6 +663,8 @@ class Model_TTree_v20(uproot.behaviors.TTree.TTree, uproot.model.VersionedModel) A :doc:`uproot.model.VersionedModel` for ``TTree`` version 20. """ + behaviors = (uproot.behaviors.TTree.TTree,) + def read_members(self, chunk, cursor, context, file): if self.is_memberwise: raise NotImplementedError( diff --git a/src/uproot/serialization.py b/src/uproot/serialization.py index 868c87e52..2d267e48c 100644 --- a/src/uproot/serialization.py +++ b/src/uproot/serialization.py @@ -39,15 +39,19 @@ def numbytes_version(num_bytes, version): def _serialize_object_any(out, model, name): - where = len(out) - model._serialize(out, True, name) + if model is None: + out.append(b"\x00\x00\x00\x00") - classname = model.classname.encode(errors="surrogateescape") + b"\x00" - num_bytes = sum(len(x) for x in out[where:]) + len(classname) + 4 - bcnt = numpy.uint32(num_bytes) | uproot.const.kByteCountMask - tag = uproot.const.kNewClassTag + else: + where = len(out) + model._serialize(out, True, name, numpy.uint32(0x00000000)) + + classname = model.classname.encode(errors="surrogateescape") + b"\x00" + num_bytes = sum(len(x) for x in out[where:]) + len(classname) + 4 + bcnt = numpy.uint32(num_bytes) | uproot.const.kByteCountMask + tag = uproot.const.kNewClassTag - out.insert(where, _serialize_object_any_format1.pack(bcnt, tag) + classname) + out.insert(where, _serialize_object_any_format1.pack(bcnt, tag) + classname) def serialize_object_any(model, name=None): diff --git a/src/uproot/sink/file.py b/src/uproot/sink/file.py index 56ad3b998..9a8c4475e 100644 --- a/src/uproot/sink/file.py +++ b/src/uproot/sink/file.py @@ -125,6 +125,13 @@ def write(self, location, serialization): self._file.seek(location) self._file.write(serialization) + def current_length(self): + tmp = self._file.tell() + self._file.seek(0, os.SEEK_END) + out = self._file.tell() + self._file.seek(tmp) + return out + def set_file_length(self, length): """ FIXME: docstring @@ -134,7 +141,6 @@ def set_file_length(self, length): self._file.seek(0, os.SEEK_END) missing = length - self._file.tell() - assert missing >= 0 if missing > 0: self._file.write(b"\x00" * missing) diff --git a/src/uproot/source/cursor.py b/src/uproot/source/cursor.py index eeb42ad85..636addc76 100644 --- a/src/uproot/source/cursor.py +++ b/src/uproot/source/cursor.py @@ -267,6 +267,24 @@ def float16(self, chunk, num_bits, context, move=True): return out.item() + def byte(self, chunk, context, move=True): + """ + Args: + chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data + from the file :doc:`uproot.source.chunk.Source`. + context (dict): Auxiliary data used in deserialization. + move (bool): If True, move the + :ref:`uproot.source.cursor.Cursor.index` past the fields; + otherwise, leave it where it is. + + Interpret data at this :ref:`uproot.source.cursor.Cursor.index` as a raw + byte. + """ + out = chunk.get(self._index, self._index + 1, self, context) + if move: + self._index += 1 + return out + def bytes(self, chunk, length, context, move=True): """ Args: @@ -340,11 +358,7 @@ def bytestring(self, chunk, context, move=True): stop = start + length if move: self._index = stop - data = chunk.get(start, stop, self, context) - if hasattr(data, "tobytes"): - return data.tobytes() - else: - return data.tostring() + return uproot._util.tobytes(chunk.get(start, stop, self, context)) def string(self, chunk, context, move=True): """ @@ -388,10 +402,7 @@ def bytestring_with_length(self, chunk, context, length, move=True): if move: self._index = stop data = chunk.get(start, stop, self, context) - if hasattr(data, "tobytes"): - return data.tobytes() - else: - return data.tostring() + return uproot._util.tobytes(data) def string_with_length(self, chunk, context, length, move=True): """ @@ -443,11 +454,7 @@ def classname(self, chunk, context, move=True): if move: self._index += local_stop - out = remainder[: local_stop - 1] - if hasattr(out, "tobytes"): - out = out.tobytes() - else: - out = out.tostring() + out = uproot._util.tobytes(remainder[: local_stop - 1]) if uproot._util.py2: return out diff --git a/src/uproot/streamers.py b/src/uproot/streamers.py index a0f097e23..5e5e81a3c 100644 --- a/src/uproot/streamers.py +++ b/src/uproot/streamers.py @@ -125,11 +125,7 @@ def _ftype_to_struct(fType): def _copy_bytes(chunk, start, stop, cursor, context): - out = chunk.get(start, stop, cursor, context) - if hasattr(out, "tobytes"): - return out.tobytes() - else: - return out.tostring() + return uproot._util.tobytes(chunk.get(start, stop, cursor, context)) _tstreamerinfo_format1 = struct.Struct(">Ii") @@ -436,11 +432,13 @@ def read_members(self, chunk, cursor, context, file): chunk, cursor, context, file, self._file, self.concrete ) - def _serialize(self, out, header, name): + writable = True + + def _serialize(self, out, header, name, tobject_flags): where = len(out) out.append(self._serialization) uproot.serialization._serialize_object_any( - out, self._members["fElements"], name + out, self._members["fElements"], None ) if header: out.insert( @@ -578,7 +576,9 @@ def read_members(self, chunk, cursor, context, file): # if (TestBit(kHasRange)) GetRange(GetTitle(),fXmin,fXmax,fFactor) pass - def _serialize(self, out, header, name): + writable = True + + def _serialize(self, out, header, name, tobject_flags): where = len(out) out.append(self._serialization) if header: diff --git a/src/uproot/writing.py b/src/uproot/writing.py index 3f0910632..aaeec4bef 100644 --- a/src/uproot/writing.py +++ b/src/uproot/writing.py @@ -15,6 +15,8 @@ except ImportError: import Queue as queue +import numpy + import uproot._util import uproot._writing import uproot.compression @@ -926,11 +928,7 @@ def copy_from( chunk = notifications.get() assert isinstance(chunk, uproot.source.chunk.Chunk) - raw_data = chunk.raw_data - if hasattr(raw_data, "tobytes"): - raw_data = raw_data.tobytes() - else: - raw_data = raw_data.tostring() + raw_data = uproot._util.tobytes(chunk.raw_data) new_name, old_key = ranges[chunk.start, chunk.stop] path = new_name.strip("/").split("/") @@ -1005,12 +1003,11 @@ def to_writable(obj): FIXME: docstring """ if isinstance(obj, uproot.model.Model): - if obj.writable: - return obj - else: - raise NotImplementedError( - "this ROOT type is not writable: " + obj.classname - ) + return obj.to_writable() + + raise NotImplementedError( + "this ROOT type is not writable: {0}".format(obj.classname) + ) elif uproot._util.isstr(obj): return to_TObjString(obj) @@ -1021,15 +1018,1315 @@ def to_writable(obj): ) +def to_TString(string): + """ + This function is for developers to create TString objects that can be + written to ROOT files, to implement conversion routines. + """ + tstring = uproot.models.TString.Model_TString(str(string)) + tstring._deeply_writable = True + tstring._cursor = None + tstring._file = None + tstring._parent = None + tstring._members = {} + tstring._bases = [] + tstring._num_bytes = None + tstring._instance_version = None + return tstring + + def to_TObjString(string): """ - FIXME: docstring + This function is for developers to create TObjString objects that can be + written to ROOT files, to implement conversion routines. + """ + tobjstring = uproot.models.TObjString.Model_TObjString(str(string)) + tobjstring._deeply_writable = True + tobjstring._cursor = None + tobjstring._parent = None + tobjstring._members = {} + tobjstring._bases = (uproot.models.TObject.Model_TObject(),) + tobjstring._num_bytes = len(string) + (1 if len(string) < 255 else 5) + 16 + tobjstring._instance_version = 1 + return tobjstring + + +def to_TList(data, name=""): + """ + Args: + data (:doc:`uproot.model.Model`): Python iterable to convert into a TList. + name (str): Name of the list (usually empty: ``""``). + + This function is for developers to create TList objects that can be + written to ROOT files, to implement conversion routines. + """ + if not all(isinstance(x, uproot.model.Model) for x in data): + raise TypeError( + "list to convert to TList must only contain ROOT objects (uproot.Model)" + ) + + tobject = uproot.models.TObject.Model_TObject.empty() + tobject._members["@fUniqueID"] = 0 + tobject._members["@fBits"] = 0 + + tlist = uproot.models.TList.Model_TList.empty() + tlist._bases.append(tobject) + tlist._members["fName"] = name + tlist._data = list(data) + tlist._members["fSize"] = len(tlist._data) + tlist._options = [b""] * len(tlist._data) + + if all(x._deeply_writable for x in tlist._data): + tlist._deeply_writable = True + + return tlist + + +def to_TArray(data): + """ + Args: + data (numpy.ndarray): The array to convert to big-endian and wrap as + TArrayC, TArrayS, TArrayI, TArrayL, TArrayF, or TArrayD, depending + on its dtype. + + This function is for developers to create TArray objects that can be + written to ROOT files, to implement conversion routines. + """ + if data.ndim != 1: + raise ValueError("data to convert to TArray must be one-dimensional") + + if issubclass(data.dtype.type, numpy.int8): + cls = uproot.models.TArray.Model_TArrayC + elif issubclass(data.dtype.type, numpy.int16): + cls = uproot.models.TArray.Model_TArrayS + elif issubclass(data.dtype.type, numpy.int32): + cls = uproot.models.TArray.Model_TArrayI + elif issubclass(data.dtype.type, numpy.int64): + cls = uproot.models.TArray.Model_TArrayL + elif issubclass(data.dtype.type, numpy.float32): + cls = uproot.models.TArray.Model_TArrayF + elif issubclass(data.dtype.type, numpy.float64): + cls = uproot.models.TArray.Model_TArrayD + else: + raise ValueError( + "data to convert to TArray must have signed integer or floating-point type, not {0}".format( + repr(data.dtype) + ) + ) + + tarray = cls.empty() + tarray._deeply_writable = True + tarray._members["fN"] = len(data) + tarray._data = data.astype(data.dtype.newbyteorder(">")) + return tarray + + +def to_TAxis( + fName, + fTitle, + fNbins, + fXmin, + fXmax, + fXbins=None, + fFirst=0, + fLast=0, + fBits2=0, + fTimeDisplay=False, + fTimeFormat="", + fLabels=None, + fModLabs=None, + fNdivisions=510, + fAxisColor=1, + fLabelColor=1, + fLabelFont=42, + fLabelOffset=0.005, + fLabelSize=0.035, + fTickLength=0.03, + fTitleOffset=1.0, + fTitleSize=0.035, + fTitleColor=1, + fTitleFont=42, +): + """ + Args: + fName (str): Internal name of axis, usually ``"xaxis"``, ``"yaxis"``, ``"zaxis"``. + fTitle (str): Internal title of axis, usually empty: ``""``. + fNbins (int): Number of bins. (https://root.cern.ch/doc/master/classTAxis.html) + fXmin (float): Low edge of first bin. + fXmax (float): Upper edge of last bin. + fXbins (None or numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Bin + edges array in X. None generates an empty array. + fFirst (int): First bin to display. 1 if no range defined NOTE: in some cases a zero is returned (see TAxis::SetRange) + fLast (int): Last bin to display. fNbins if no range defined NOTE: in some cases a zero is returned (see TAxis::SetRange) + fBits2 (int): Second bit status word. + fTimeDisplay (bool): On/off displaying time values instead of numerics. + fTimeFormat (str or :doc:`uproot.models.TString.Model_TString`): Date&time format, ex: 09/12/99 12:34:00. + fLabels (None or :doc:`uproot.models.THashList.Model_THashList`): List of labels. + fModLabs (None or :doc:`uproot.models.List.Model_TList`): List of modified labels. + fNdivisions (int): Number of divisions(10000*n3 + 100*n2 + n1). (https://root.cern.ch/doc/master/classTAttAxis.html) + fAxisColor (int): Color of the line axis. + fLabelColor (int): Color of labels. + fLabelFont (int): Font for labels. + fLabelOffset (float): Offset of labels. + fLabelSize (float): Size of labels. + fTickLength (float): Length of tick marks. + fTitleOffset (float): Offset of axis title. + fTitleSize (float): Size of axis title. + fTitleColor (int): Color of axis title. + fTitleFont (int): Font for axis title. + + This function is for developers to create TAxis objects that can be + written to ROOT files, to implement conversion routines. + """ + tobject = uproot.models.TObject.Model_TObject.empty() + tobject._members["@fUniqueID"] = 0 + tobject._members["@fBits"] = 0 + + tnamed = uproot.models.TNamed.Model_TNamed.empty() + tnamed._deeply_writable = True + tnamed._bases.append(tobject) + tnamed._members["fName"] = fName + tnamed._members["fTitle"] = fTitle + + tattaxis = uproot.models.TAtt.Model_TAttAxis_v4.empty() + tattaxis._deeply_writable = True + tattaxis._members["fNdivisions"] = fNdivisions + tattaxis._members["fAxisColor"] = fAxisColor + tattaxis._members["fLabelColor"] = fLabelColor + tattaxis._members["fLabelFont"] = fLabelFont + tattaxis._members["fLabelOffset"] = fLabelOffset + tattaxis._members["fLabelSize"] = fLabelSize + tattaxis._members["fTickLength"] = fTickLength + tattaxis._members["fTitleOffset"] = fTitleOffset + tattaxis._members["fTitleSize"] = fTitleSize + tattaxis._members["fTitleColor"] = fTitleColor + tattaxis._members["fTitleFont"] = fTitleFont + + if fXbins is None: + fXbins = numpy.array([], dtype=numpy.float64) + + if isinstance(fXbins, uproot.models.TArray.Model_TArrayD): + tarray_fXbins = fXbins + else: + tarray_fXbins = to_TArray(fXbins) + + if isinstance(fTimeFormat, uproot.models.TString.Model_TString): + tstring_fTimeFormat = fTimeFormat + else: + tstring_fTimeFormat = to_TString(fTimeFormat) + + taxis = uproot.models.TH.Model_TAxis_v10.empty() + taxis._deeply_writable = True + taxis._bases.append(tnamed) + taxis._bases.append(tattaxis) + taxis._members["fNbins"] = fNbins + taxis._members["fXmin"] = fXmin + taxis._members["fXmax"] = fXmax + taxis._members["fXbins"] = tarray_fXbins + taxis._members["fFirst"] = fFirst + taxis._members["fLast"] = fLast + taxis._members["fBits2"] = fBits2 + taxis._members["fTimeDisplay"] = fTimeDisplay + taxis._members["fTimeFormat"] = tstring_fTimeFormat + taxis._members["fLabels"] = fLabels + taxis._members["fModLabs"] = fModLabs + + return taxis + + +def to_TH1x( + fName, + fTitle, + data, + fEntries, + fTsumw, + fTsumw2, + fTsumwx, + fTsumwx2, + fSumw2, + fXaxis, + fYaxis=None, + fZaxis=None, + fNcells=None, + fBarOffset=0, + fBarWidth=1000, + fMaximum=-1111.0, + fMinimum=-1111.0, + fNormFactor=0.0, + fContour=None, + fOption="", + fFunctions=None, + fBufferSize=0, + fBuffer=None, + fBinStatErrOpt=0, + fStatOverflows=2, + fLineColor=602, + fLineStyle=1, + fLineWidth=1, + fFillColor=0, + fFillStyle=1001, + fMarkerColor=1, + fMarkerStyle=1, + fMarkerSize=1.0, +): """ - out = uproot.models.TObjString.Model_TObjString(str(string)) - out._cursor = None - out._parent = None - out._members = {} - out._bases = (uproot.models.TObject.Model_TObject(),) - out._num_bytes = len(string) + (1 if len(string) < 255 else 5) + 16 - out._instance_version = 1 - return out + Args: + fName (None or str): Temporary name, will be overwritten by the writing + process because Uproot's write syntax is ``file[name] = histogram``. + fTitle (str): Real title of the histogram. + data (numpy.ndarray or :doc:`uproot.models.TArray.Model_TArray`): Bin contents + with first bin as underflow, last bin as overflow. The dtype of this array + determines the return type of this function (TH1C, TH1D, TH1F, TH1I, or TH1S). + fEntries (float): Number of entries. (https://root.cern.ch/doc/master/classTH1.html) + fTsumw (float): Total Sum of weights. + fTsumw2 (float): Total Sum of squares of weights. + fTsumwx (float): Total Sum of weight*X. + fTsumwx2 (float): Total Sum of weight*X*X. + fSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights. + fXaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="xaxis"`` and ``fTitle=""``. + fYaxis (None or :doc:`uproot.models.TH.Model_TAxis_v10`): None generates a + default for 1D histograms. + fZaxis (None or :doc:`uproot.models.TH.Model_TAxis_v10`): None generates a + default for 1D and 2D histograms. + fNcells (None or int): Number of bins(1D), cells (2D) +U/Overflows. Computed + from ``data`` if None. + fBarOffset (int): (1000*offset) for bar charts or legos + fBarWidth (int): (1000*width) for bar charts or legos + fMaximum (float): Maximum value for plotting. + fMinimum (float): Minimum value for plotting. + fNormFactor (float): Normalization factor. + fContour (None or numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + to display contour levels. None generates an empty array. + fOption (str or :doc:`uproot.models.TString.Model_TString`): Histogram options. + fFunctions (None, list, or :doc:`uproot.models.TList.Model_TList`): ->Pointer to + list of functions (fits and user). None generates an empty list. + fBufferSize (None or int): fBuffer size. Computed from ``fBuffer`` if None. + fBuffer (None or numpy.ndarray of numpy.float64): Buffer of entries accumulated + before automatically choosing the binning. (Irrelevant for serialization?) + None generates an empty array. + fBinStatErrOpt (int): Option for bin statistical errors. + fStatOverflows (int): Per object flag to use under/overflows in statistics. + fLineColor (int): Line color. (https://root.cern.ch/doc/master/classTAttLine.html) + fLineStyle (int): Line style. + fLineWidth (int): Line width. + fFillColor (int): Fill area color. (https://root.cern.ch/doc/master/classTAttFill.html) + fFillStyle (int): Fill area style. + fMarkerColor (int): Marker color. (https://root.cern.ch/doc/master/classTAttMarker.html) + fMarkerStyle (int): Marker style. + fMarkerSize (float): Marker size. + + This function is for developers to create TH1* objects that can be + written to ROOT files, to implement conversion routines. The choice of + TH1C, TH1D, TH1F, TH1I, or TH1S depends on the dtype of the ``data`` array. + """ + tobject = uproot.models.TObject.Model_TObject.empty() + tobject._members["@fUniqueID"] = 0 + tobject._members["@fBits"] = 0 + + tnamed = uproot.models.TNamed.Model_TNamed.empty() + tnamed._deeply_writable = True + tnamed._bases.append(tobject) + tnamed._members["fName"] = fName + tnamed._members["fTitle"] = fTitle + + tattline = uproot.models.TAtt.Model_TAttLine_v2.empty() + tattline._deeply_writable = True + tattline._members["fLineColor"] = fLineColor + tattline._members["fLineStyle"] = fLineStyle + tattline._members["fLineWidth"] = fLineWidth + + tattfill = uproot.models.TAtt.Model_TAttFill_v2.empty() + tattfill._deeply_writable = True + tattfill._members["fFillColor"] = fFillColor + tattfill._members["fFillStyle"] = fFillStyle + + tattmarker = uproot.models.TAtt.Model_TAttMarker_v2.empty() + tattmarker._deeply_writable = True + tattmarker._members["fMarkerColor"] = fMarkerColor + tattmarker._members["fMarkerStyle"] = fMarkerStyle + tattmarker._members["fMarkerSize"] = fMarkerSize + + th1 = uproot.models.TH.Model_TH1_v8.empty() + + th1._bases.append(tnamed) + th1._bases.append(tattline) + th1._bases.append(tattfill) + th1._bases.append(tattmarker) + + if fYaxis is None: + fYaxis = to_TAxis(fName="yaxis", fTitle="", fNbins=1, fXmin=0.0, fXmax=1.0) + if fZaxis is None: + fZaxis = to_TAxis(fName="zaxis", fTitle="", fNbins=1, fXmin=0.0, fXmax=1.0) + if fContour is None: + fContour = numpy.array([], dtype=numpy.float64) + if fFunctions is None: + fFunctions = [] + if fBuffer is None: + fBuffer = numpy.array([], dtype=numpy.float64) + + if isinstance(data, uproot.models.TArray.Model_TArray): + tarray_data = data + else: + tarray_data = to_TArray(data) + + if isinstance(fSumw2, uproot.models.TArray.Model_TArray): + tarray_fSumw2 = fSumw2 + else: + tarray_fSumw2 = to_TArray(fSumw2) + if not isinstance(tarray_fSumw2, uproot.models.TArray.Model_TArrayD): + raise TypeError("fSumw2 must be an array of float64 (TArrayD)") + + if isinstance(fContour, uproot.models.TArray.Model_TArray): + tarray_fContour = fContour + else: + tarray_fContour = to_TArray(fContour) + if not isinstance(tarray_fContour, uproot.models.TArray.Model_TArrayD): + raise TypeError("fContour must be an array of float64 (TArrayD)") + + if isinstance(fOption, uproot.models.TString.Model_TString): + tstring_fOption = fOption + else: + tstring_fOption = to_TString(fOption) + + if isinstance(fFunctions, uproot.models.TList.Model_TList): + tlist_fFunctions = fFunctions + else: + tlist_fFunctions = to_TList(fFunctions, name="") + # FIXME: require all list items to be the appropriate class (TFunction?) + + th1._members["fNcells"] = len(tarray_data) if fNcells is None else fNcells + th1._members["fXaxis"] = fXaxis + th1._members["fYaxis"] = fYaxis + th1._members["fZaxis"] = fZaxis + th1._members["fBarOffset"] = fBarOffset + th1._members["fBarWidth"] = fBarWidth + th1._members["fEntries"] = fEntries + th1._members["fTsumw"] = fTsumw + th1._members["fTsumw2"] = fTsumw2 + th1._members["fTsumwx"] = fTsumwx + th1._members["fTsumwx2"] = fTsumwx2 + th1._members["fMaximum"] = fMaximum + th1._members["fMinimum"] = fMinimum + th1._members["fNormFactor"] = fNormFactor + th1._members["fContour"] = tarray_fContour + th1._members["fSumw2"] = tarray_fSumw2 + th1._members["fOption"] = tstring_fOption + th1._members["fFunctions"] = tlist_fFunctions + th1._members["fBufferSize"] = len(fBuffer) if fBufferSize is None else fBufferSize + th1._members["fBuffer"] = fBuffer + th1._members["fBinStatErrOpt"] = fBinStatErrOpt + th1._members["fStatOverflows"] = fStatOverflows + + th1._speedbump1 = b"\x00" + + th1._deeply_writable = tlist_fFunctions._deeply_writable + + if isinstance(tarray_data, uproot.models.TArray.Model_TArrayC): + cls = uproot.models.TH.Model_TH1C_v3 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayS): + cls = uproot.models.TH.Model_TH1S_v3 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayI): + cls = uproot.models.TH.Model_TH1I_v3 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayF): + cls = uproot.models.TH.Model_TH1F_v3 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayD): + cls = uproot.models.TH.Model_TH1D_v3 + else: + raise TypeError( + "no TH1* subclasses correspond to {0}".format(tarray_data.classname) + ) + + th1x = cls.empty() + th1x._bases.append(th1) + th1x._bases.append(tarray_data) + + th1x._deeply_writable = th1._deeply_writable + + return th1x + + +def to_TH2x( + fName, + fTitle, + data, + fEntries, + fTsumw, + fTsumw2, + fTsumwx, + fTsumwx2, + fTsumwy, + fTsumwy2, + fTsumwxy, + fSumw2, + fXaxis, + fYaxis, + fZaxis=None, + fScalefactor=1.0, + fNcells=None, + fBarOffset=0, + fBarWidth=1000, + fMaximum=-1111.0, + fMinimum=-1111.0, + fNormFactor=0.0, + fContour=None, + fOption="", + fFunctions=None, + fBufferSize=0, + fBuffer=None, + fBinStatErrOpt=0, + fStatOverflows=2, + fLineColor=602, + fLineStyle=1, + fLineWidth=1, + fFillColor=0, + fFillStyle=1001, + fMarkerColor=1, + fMarkerStyle=1, + fMarkerSize=1.0, +): + """ + Args: + fName (None or str): Temporary name, will be overwritten by the writing + process because Uproot's write syntax is ``file[name] = histogram``. + fTitle (str): Real title of the histogram. + data (numpy.ndarray or :doc:`uproot.models.TArray.Model_TArray`): Bin contents + with first bin as underflow, last bin as overflow. The dtype of this array + determines the return type of this function (TH2C, TH2D, TH2F, TH2I, or TH2S). + fEntries (float): Number of entries. (https://root.cern.ch/doc/master/classTH1.html) + fTsumw (float): Total Sum of weights. + fTsumw2 (float): Total Sum of squares of weights. + fTsumwx (float): Total Sum of weight*X. + fTsumwx2 (float): Total Sum of weight*X*X. + fTsumwy (float): Total Sum of weight*Y. (TH2 only: https://root.cern.ch/doc/master/classTH2.html) + fTsumwy2 (float): Total Sum of weight*Y*Y. (TH2 only.) + fTsumwxy (float): Total Sum of weight*X*Y. (TH2 only.) + fSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights. + fXaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="xaxis"`` and ``fTitle=""``. + fYaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="yaxis"`` and ``fTitle=""``. + fZaxis (None or :doc:`uproot.models.TH.Model_TAxis_v10`): None generates a + default for 1D and 2D histograms. + fScalefactor (float): Scale factor. (TH2 only.) + fNcells (None or int): Number of bins(1D), cells (2D) +U/Overflows. Computed + from ``data`` if None. + fBarOffset (int): (1000*offset) for bar charts or legos + fBarWidth (int): (1000*width) for bar charts or legos + fMaximum (float): Maximum value for plotting. + fMinimum (float): Minimum value for plotting. + fNormFactor (float): Normalization factor. + fContour (None or numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + to display contour levels. None generates an empty array. + fOption (str or :doc:`uproot.models.TString.Model_TString`): Histogram options. + fFunctions (None, list, or :doc:`uproot.models.TList.Model_TList`): ->Pointer to + list of functions (fits and user). None generates an empty list. + fBufferSize (None or int): fBuffer size. Computed from ``fBuffer`` if None. + fBuffer (None or numpy.ndarray of numpy.float64): Buffer of entries accumulated + before automatically choosing the binning. (Irrelevant for serialization?) + None generates an empty array. + fBinStatErrOpt (int): Option for bin statistical errors. + fStatOverflows (int): Per object flag to use under/overflows in statistics. + fLineColor (int): Line color. (https://root.cern.ch/doc/master/classTAttLine.html) + fLineStyle (int): Line style. + fLineWidth (int): Line width. + fFillColor (int): Fill area color. (https://root.cern.ch/doc/master/classTAttFill.html) + fFillStyle (int): Fill area style. + fMarkerColor (int): Marker color. (https://root.cern.ch/doc/master/classTAttMarker.html) + fMarkerStyle (int): Marker style. + fMarkerSize (float): Marker size. + + This function is for developers to create TH2* objects that can be + written to ROOT files, to implement conversion routines. The choice of + TH2C, TH2D, TH2F, TH2I, or TH2S depends on the dtype of the ``data`` array. + """ + th1x = to_TH1x( + fName=fName, + fTitle=fTitle, + data=data, + fEntries=fEntries, + fTsumw=fTsumw, + fTsumw2=fTsumw2, + fTsumwx=fTsumwx, + fTsumwx2=fTsumwx2, + fSumw2=fSumw2, + fXaxis=fXaxis, + fYaxis=fYaxis, + fZaxis=fZaxis, + fNcells=fNcells, + fBarOffset=fBarOffset, + fBarWidth=fBarWidth, + fMaximum=fMaximum, + fMinimum=fMinimum, + fNormFactor=fNormFactor, + fContour=fContour, + fOption=fOption, + fFunctions=fFunctions, + fBufferSize=fBufferSize, + fBuffer=fBuffer, + fBinStatErrOpt=fBinStatErrOpt, + fStatOverflows=fStatOverflows, + fLineColor=fLineColor, + fLineStyle=fLineStyle, + fLineWidth=fLineWidth, + fFillColor=fFillColor, + fFillStyle=fFillStyle, + fMarkerColor=fMarkerColor, + fMarkerStyle=fMarkerStyle, + fMarkerSize=fMarkerSize, + ) + + th1 = th1x._bases[0] + tarray_data = th1x._bases[1] + + th2 = uproot.models.TH.Model_TH2_v5.empty() + th2._bases.append(th1) + th2._members["fScalefactor"] = fScalefactor + th2._members["fTsumwy"] = fTsumwy + th2._members["fTsumwy2"] = fTsumwy2 + th2._members["fTsumwxy"] = fTsumwxy + + if isinstance(tarray_data, uproot.models.TArray.Model_TArrayC): + cls = uproot.models.TH.Model_TH2C_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayS): + cls = uproot.models.TH.Model_TH2S_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayI): + cls = uproot.models.TH.Model_TH2I_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayF): + cls = uproot.models.TH.Model_TH2F_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayD): + cls = uproot.models.TH.Model_TH2D_v4 + else: + raise TypeError( + "no TH2* subclasses correspond to {0}".format(tarray_data.classname) + ) + + th2x = cls.empty() + th2x._bases.append(th2) + th2x._bases.append(tarray_data) + + th2._deeply_writable = th1._deeply_writable + th2x._deeply_writable = th2._deeply_writable + + return th2x + + +def to_TH3x( + fName, + fTitle, + data, + fEntries, + fTsumw, + fTsumw2, + fTsumwx, + fTsumwx2, + fTsumwy, + fTsumwy2, + fTsumwxy, + fTsumwz, + fTsumwz2, + fTsumwxz, + fTsumwyz, + fSumw2, + fXaxis, + fYaxis, + fZaxis, + fNcells=None, + fBarOffset=0, + fBarWidth=1000, + fMaximum=-1111.0, + fMinimum=-1111.0, + fNormFactor=0.0, + fContour=None, + fOption="", + fFunctions=None, + fBufferSize=0, + fBuffer=None, + fBinStatErrOpt=0, + fStatOverflows=2, + fLineColor=602, + fLineStyle=1, + fLineWidth=1, + fFillColor=0, + fFillStyle=1001, + fMarkerColor=1, + fMarkerStyle=1, + fMarkerSize=1.0, +): + """ + Args: + fName (None or str): Temporary name, will be overwritten by the writing + process because Uproot's write syntax is ``file[name] = histogram``. + fTitle (str): Real title of the histogram. + data (numpy.ndarray or :doc:`uproot.models.TArray.Model_TArray`): Bin contents + with first bin as underflow, last bin as overflow. The dtype of this array + determines the return type of this function (TH3C, TH3D, TH3F, TH3I, or TH3S). + fEntries (float): Number of entries. (https://root.cern.ch/doc/master/classTH1.html) + fTsumw (float): Total Sum of weights. + fTsumw2 (float): Total Sum of squares of weights. + fTsumwx (float): Total Sum of weight*X. + fTsumwx2 (float): Total Sum of weight*X*X. + fTsumwy (float): Total Sum of weight*Y. (TH3 only: https://root.cern.ch/doc/master/classTH3.html) + fTsumwy2 (float): Total Sum of weight*Y*Y. (TH3 only.) + fTsumwxy (float): Total Sum of weight*X*Y. (TH3 only.) + fTsumwz (float): Total Sum of weight*Z. (TH3 only.) + fTsumwz2 (float): Total Sum of weight*Z*Z. (TH3 only.) + fTsumwxz (float): Total Sum of weight*X*Z. (TH3 only.) + fTsumwyz (float): Total Sum of weight*Y*Z. (TH3 only.) + fSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights. + fXaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="xaxis"`` and ``fTitle=""``. + fYaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="yaxis"`` and ``fTitle=""``. + fZaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="zaxis"`` and ``fTitle=""``. + fNcells (None or int): Number of bins(1D), cells (2D) +U/Overflows. Computed + from ``data`` if None. + fBarOffset (int): (1000*offset) for bar charts or legos + fBarWidth (int): (1000*width) for bar charts or legos + fMaximum (float): Maximum value for plotting. + fMinimum (float): Minimum value for plotting. + fNormFactor (float): Normalization factor. + fContour (None or numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + to display contour levels. None generates an empty array. + fOption (str or :doc:`uproot.models.TString.Model_TString`): Histogram options. + fFunctions (None, list, or :doc:`uproot.models.TList.Model_TList`): ->Pointer to + list of functions (fits and user). None generates an empty list. + fBufferSize (None or int): fBuffer size. Computed from ``fBuffer`` if None. + fBuffer (None or numpy.ndarray of numpy.float64): Buffer of entries accumulated + before automatically choosing the binning. (Irrelevant for serialization?) + None generates an empty array. + fBinStatErrOpt (int): Option for bin statistical errors. + fStatOverflows (int): Per object flag to use under/overflows in statistics. + fLineColor (int): Line color. (https://root.cern.ch/doc/master/classTAttLine.html) + fLineStyle (int): Line style. + fLineWidth (int): Line width. + fFillColor (int): Fill area color. (https://root.cern.ch/doc/master/classTAttFill.html) + fFillStyle (int): Fill area style. + fMarkerColor (int): Marker color. (https://root.cern.ch/doc/master/classTAttMarker.html) + fMarkerStyle (int): Marker style. + fMarkerSize (float): Marker size. + + This function is for developers to create TH3* objects that can be + written to ROOT files, to implement conversion routines. The choice of + TH3C, TH3D, TH3F, TH3I, or TH3S depends on the dtype of the ``data`` array. + """ + th1x = to_TH1x( + fName=fName, + fTitle=fTitle, + data=data, + fEntries=fEntries, + fTsumw=fTsumw, + fTsumw2=fTsumw2, + fTsumwx=fTsumwx, + fTsumwx2=fTsumwx2, + fSumw2=fSumw2, + fXaxis=fXaxis, + fYaxis=fYaxis, + fZaxis=fZaxis, + fNcells=fNcells, + fBarOffset=fBarOffset, + fBarWidth=fBarWidth, + fMaximum=fMaximum, + fMinimum=fMinimum, + fNormFactor=fNormFactor, + fContour=fContour, + fOption=fOption, + fFunctions=fFunctions, + fBufferSize=fBufferSize, + fBuffer=fBuffer, + fBinStatErrOpt=fBinStatErrOpt, + fStatOverflows=fStatOverflows, + fLineColor=fLineColor, + fLineStyle=fLineStyle, + fLineWidth=fLineWidth, + fFillColor=fFillColor, + fFillStyle=fFillStyle, + fMarkerColor=fMarkerColor, + fMarkerStyle=fMarkerStyle, + fMarkerSize=fMarkerSize, + ) + + th1 = th1x._bases[0] + tarray_data = th1x._bases[1] + + tatt3d = uproot.models.TAtt.Model_TAtt3D_v1.empty() + tatt3d._deeply_writable = True + + th3 = uproot.models.TH.Model_TH3_v6.empty() + th3._bases.append(th1) + th3._bases.append(tatt3d) + th3._members["fTsumwy"] = fTsumwy + th3._members["fTsumwy2"] = fTsumwy2 + th3._members["fTsumwxy"] = fTsumwxy + th3._members["fTsumwz"] = fTsumwz + th3._members["fTsumwz2"] = fTsumwz2 + th3._members["fTsumwxz"] = fTsumwxz + th3._members["fTsumwyz"] = fTsumwyz + + if isinstance(tarray_data, uproot.models.TArray.Model_TArrayC): + cls = uproot.models.TH.Model_TH3C_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayS): + cls = uproot.models.TH.Model_TH3S_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayI): + cls = uproot.models.TH.Model_TH3I_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayF): + cls = uproot.models.TH.Model_TH3F_v4 + elif isinstance(tarray_data, uproot.models.TArray.Model_TArrayD): + cls = uproot.models.TH.Model_TH3D_v4 + else: + raise TypeError( + "no TH3* subclasses correspond to {0}".format(tarray_data.classname) + ) + + th3x = cls.empty() + th3x._bases.append(th3) + th3x._bases.append(tarray_data) + + th3._deeply_writable = th1._deeply_writable + th3x._deeply_writable = th3._deeply_writable + + return th3x + + +def to_TProfile( + fName, + fTitle, + data, + fEntries, + fTsumw, + fTsumw2, + fTsumwx, + fTsumwx2, + fTsumwy, + fTsumwy2, + fSumw2, + fBinEntries, + fBinSumw2, + fXaxis, + fYaxis=None, + fZaxis=None, + fYmin=0.0, + fYmax=0.0, + fErrorMode=0, + fNcells=None, + fBarOffset=0, + fBarWidth=1000, + fMaximum=-1111.0, + fMinimum=-1111.0, + fNormFactor=0.0, + fContour=None, + fOption="", + fFunctions=None, + fBufferSize=0, + fBuffer=None, + fBinStatErrOpt=0, + fStatOverflows=2, + fLineColor=602, + fLineStyle=1, + fLineWidth=1, + fFillColor=0, + fFillStyle=1001, + fMarkerColor=1, + fMarkerStyle=1, + fMarkerSize=1.0, +): + """ + Args: + fName (None or str): Temporary name, will be overwritten by the writing + process because Uproot's write syntax is ``file[name] = histogram``. + fTitle (str): Real title of the histogram. + data (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Bin + contents with first bin as underflow, last bin as overflow. The dtype of this array + must be float64. + fEntries (float): Number of entries. (https://root.cern.ch/doc/master/classTH1.html) + fTsumw (float): Total Sum of weights. + fTsumw2 (float): Total Sum of squares of weights. + fTsumwx (float): Total Sum of weight*X. + fTsumwx2 (float): Total Sum of weight*X*X. + fTsumwy (float): Total Sum of weight*Y. (TProfile only: https://root.cern.ch/doc/master/classTProfile.html) + fTsumwy2 (float): Total Sum of weight*Y*Y. (TProfile only.) + fSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights. + fBinEntries (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Number + of entries per bin. (TProfile only.) + fBinSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights per bin. (TProfile only.) + fXaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="xaxis"`` and ``fTitle=""``. + fYaxis (None or :doc:`uproot.models.TH.Model_TAxis_v10`): None generates a + default for 1D histograms. + fZaxis (None or :doc:`uproot.models.TH.Model_TAxis_v10`): None generates a + default for 1D and 2D histograms. + fYmin (float): Lower limit in Y (if set). (TProfile only.) + fYmax (float): Upper limit in Y (if set). (TProfile only.) + fErrorMode (int): Option to compute errors. (TProfile only.) + fNcells (None or int): Number of bins(1D), cells (2D) +U/Overflows. Computed + from ``data`` if None. + fBarOffset (int): (1000*offset) for bar charts or legos + fBarWidth (int): (1000*width) for bar charts or legos + fMaximum (float): Maximum value for plotting. + fMinimum (float): Minimum value for plotting. + fNormFactor (float): Normalization factor. + fContour (None or numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + to display contour levels. None generates an empty array. + fOption (str or :doc:`uproot.models.TString.Model_TString`): Histogram options. + fFunctions (None, list, or :doc:`uproot.models.TList.Model_TList`): ->Pointer to + list of functions (fits and user). None generates an empty list. + fBufferSize (None or int): fBuffer size. Computed from ``fBuffer`` if None. + fBuffer (None or numpy.ndarray of numpy.float64): Buffer of entries accumulated + before automatically choosing the binning. (Irrelevant for serialization?) + None generates an empty array. + fBinStatErrOpt (int): Option for bin statistical errors. + fStatOverflows (int): Per object flag to use under/overflows in statistics. + fLineColor (int): Line color. (https://root.cern.ch/doc/master/classTAttLine.html) + fLineStyle (int): Line style. + fLineWidth (int): Line width. + fFillColor (int): Fill area color. (https://root.cern.ch/doc/master/classTAttFill.html) + fFillStyle (int): Fill area style. + fMarkerColor (int): Marker color. (https://root.cern.ch/doc/master/classTAttMarker.html) + fMarkerStyle (int): Marker style. + fMarkerSize (float): Marker size. + + This function is for developers to create TProfile objects that can be + written to ROOT files, to implement conversion routines. + """ + th1x = to_TH1x( + fName=fName, + fTitle=fTitle, + data=data, + fEntries=fEntries, + fTsumw=fTsumw, + fTsumw2=fTsumw2, + fTsumwx=fTsumwx, + fTsumwx2=fTsumwx2, + fSumw2=fSumw2, + fXaxis=fXaxis, + fYaxis=fYaxis, + fZaxis=fZaxis, + fNcells=fNcells, + fBarOffset=fBarOffset, + fBarWidth=fBarWidth, + fMaximum=fMaximum, + fMinimum=fMinimum, + fNormFactor=fNormFactor, + fContour=fContour, + fOption=fOption, + fFunctions=fFunctions, + fBufferSize=fBufferSize, + fBuffer=fBuffer, + fBinStatErrOpt=fBinStatErrOpt, + fStatOverflows=fStatOverflows, + fLineColor=fLineColor, + fLineStyle=fLineStyle, + fLineWidth=fLineWidth, + fFillColor=fFillColor, + fFillStyle=fFillStyle, + fMarkerColor=fMarkerColor, + fMarkerStyle=fMarkerStyle, + fMarkerSize=fMarkerSize, + ) + if not isinstance(th1x, uproot.models.TH.Model_TH1D_v3): + raise TypeError("TProfile requires an array of float64 (TArrayD)") + + if isinstance(fBinEntries, uproot.models.TArray.Model_TArray): + tarray_fBinEntries = fBinEntries + else: + tarray_fBinEntries = to_TArray(fBinEntries) + if not isinstance(tarray_fBinEntries, uproot.models.TArray.Model_TArrayD): + raise TypeError("fBinEntries must be an array of float64 (TArrayD)") + + if isinstance(fBinSumw2, uproot.models.TArray.Model_TArray): + tarray_fBinSumw2 = fBinSumw2 + else: + tarray_fBinSumw2 = to_TArray(fBinSumw2) + if not isinstance(tarray_fBinSumw2, uproot.models.TArray.Model_TArrayD): + raise TypeError("fBinSumw2 must be an array of float64 (TArrayD)") + + tprofile = uproot.models.TH.Model_TProfile_v7.empty() + tprofile._bases.append(th1x) + tprofile._members["fBinEntries"] = tarray_fBinEntries + tprofile._members["fErrorMode"] = fErrorMode + tprofile._members["fYmin"] = fYmin + tprofile._members["fYmax"] = fYmax + tprofile._members["fTsumwy"] = fTsumwy + tprofile._members["fTsumwy2"] = fTsumwy2 + tprofile._members["fBinSumw2"] = tarray_fBinSumw2 + + tprofile._deeply_writable = th1x._deeply_writable + + return tprofile + + +def to_TProfile2D( + fName, + fTitle, + data, + fEntries, + fTsumw, + fTsumw2, + fTsumwx, + fTsumwx2, + fTsumwy, + fTsumwy2, + fTsumwxy, + fTsumwz, + fTsumwz2, + fSumw2, + fBinEntries, + fBinSumw2, + fXaxis, + fYaxis, + fZaxis=None, + fScalefactor=1.0, + fZmin=0.0, + fZmax=0.0, + fErrorMode=0, + fNcells=None, + fBarOffset=0, + fBarWidth=1000, + fMaximum=-1111.0, + fMinimum=-1111.0, + fNormFactor=0.0, + fContour=None, + fOption="", + fFunctions=None, + fBufferSize=0, + fBuffer=None, + fBinStatErrOpt=0, + fStatOverflows=2, + fLineColor=602, + fLineStyle=1, + fLineWidth=1, + fFillColor=0, + fFillStyle=1001, + fMarkerColor=1, + fMarkerStyle=1, + fMarkerSize=1.0, +): + """ + Args: + fName (None or str): Temporary name, will be overwritten by the writing + process because Uproot's write syntax is ``file[name] = histogram``. + fTitle (str): Real title of the histogram. + data (numpy.ndarray or :doc:`uproot.models.TArray.Model_TArray`): Bin contents + with first bin as underflow, last bin as overflow. The dtype of this array + must be float64. + fEntries (float): Number of entries. (https://root.cern.ch/doc/master/classTH1.html) + fTsumw (float): Total Sum of weights. + fTsumw2 (float): Total Sum of squares of weights. + fTsumwx (float): Total Sum of weight*X. + fTsumwx2 (float): Total Sum of weight*X*X. + fTsumwy (float): Total Sum of weight*Y. (TH2 only: https://root.cern.ch/doc/master/classTH2.html) + fTsumwy2 (float): Total Sum of weight*Y*Y. (TH2 only.) + fTsumwxy (float): Total Sum of weight*X*Y. (TH2 only.) + fTsumwz (float): Total Sum of weight*Z. (TProfile2D only: https://root.cern.ch/doc/master/classTProfile2D.html) + fTsumwz2 (float): Total Sum of weight*Z*Z. (TProfile2D only.) + fSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights. + fBinEntries (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Number + of entries per bin. (TProfile2D only.) + fBinSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights per bin. (TProfile2D only.) + fXaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="xaxis"`` and ``fTitle=""``. + fYaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="yaxis"`` and ``fTitle=""``. + fZaxis (None or :doc:`uproot.models.TH.Model_TAxis_v10`): None generates a + default for 1D and 2D histograms. + fScalefactor (float): Scale factor. (TH2 only.) + fZmin (float): Lower limit in Z (if set). (TProfile2D only.) + fZmax (float): Upper limit in Z (if set). (TProfile2D only.) + fErrorMode (int): Option to compute errors. (TProfile2D only.) + fNcells (None or int): Number of bins(1D), cells (2D) +U/Overflows. Computed + from ``data`` if None. + fBarOffset (int): (1000*offset) for bar charts or legos + fBarWidth (int): (1000*width) for bar charts or legos + fMaximum (float): Maximum value for plotting. + fMinimum (float): Minimum value for plotting. + fNormFactor (float): Normalization factor. + fContour (None or numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + to display contour levels. None generates an empty array. + fOption (str or :doc:`uproot.models.TString.Model_TString`): Histogram options. + fFunctions (None, list, or :doc:`uproot.models.TList.Model_TList`): ->Pointer to + list of functions (fits and user). None generates an empty list. + fBufferSize (None or int): fBuffer size. Computed from ``fBuffer`` if None. + fBuffer (None or numpy.ndarray of numpy.float64): Buffer of entries accumulated + before automatically choosing the binning. (Irrelevant for serialization?) + None generates an empty array. + fBinStatErrOpt (int): Option for bin statistical errors. + fStatOverflows (int): Per object flag to use under/overflows in statistics. + fLineColor (int): Line color. (https://root.cern.ch/doc/master/classTAttLine.html) + fLineStyle (int): Line style. + fLineWidth (int): Line width. + fFillColor (int): Fill area color. (https://root.cern.ch/doc/master/classTAttFill.html) + fFillStyle (int): Fill area style. + fMarkerColor (int): Marker color. (https://root.cern.ch/doc/master/classTAttMarker.html) + fMarkerStyle (int): Marker style. + fMarkerSize (float): Marker size. + + This function is for developers to create TProfile2D objects that can be + written to ROOT files, to implement conversion routines. + """ + th2x = to_TH2x( + fName=fName, + fTitle=fTitle, + data=data, + fEntries=fEntries, + fTsumw=fTsumw, + fTsumw2=fTsumw2, + fTsumwx=fTsumwx, + fTsumwx2=fTsumwx2, + fTsumwy=fTsumwy, + fTsumwy2=fTsumwy2, + fTsumwxy=fTsumwxy, + fSumw2=fSumw2, + fXaxis=fXaxis, + fYaxis=fYaxis, + fZaxis=fZaxis, + fScalefactor=fScalefactor, + fNcells=fNcells, + fBarOffset=fBarOffset, + fBarWidth=fBarWidth, + fMaximum=fMaximum, + fMinimum=fMinimum, + fNormFactor=fNormFactor, + fContour=fContour, + fOption=fOption, + fFunctions=fFunctions, + fBufferSize=fBufferSize, + fBuffer=fBuffer, + fBinStatErrOpt=fBinStatErrOpt, + fStatOverflows=fStatOverflows, + fLineColor=fLineColor, + fLineStyle=fLineStyle, + fLineWidth=fLineWidth, + fFillColor=fFillColor, + fFillStyle=fFillStyle, + fMarkerColor=fMarkerColor, + fMarkerStyle=fMarkerStyle, + fMarkerSize=fMarkerSize, + ) + if not isinstance(th2x, uproot.models.TH.Model_TH2D_v4): + raise TypeError("TProfile2D requires an array of float64 (TArrayD)") + + if isinstance(fBinEntries, uproot.models.TArray.Model_TArray): + tarray_fBinEntries = fBinEntries + else: + tarray_fBinEntries = to_TArray(fBinEntries) + if not isinstance(tarray_fBinEntries, uproot.models.TArray.Model_TArrayD): + raise TypeError("fBinEntries must be an array of float64 (TArrayD)") + + if isinstance(fBinSumw2, uproot.models.TArray.Model_TArray): + tarray_fBinSumw2 = fBinSumw2 + else: + tarray_fBinSumw2 = to_TArray(fBinSumw2) + if not isinstance(tarray_fBinSumw2, uproot.models.TArray.Model_TArrayD): + raise TypeError("fBinSumw2 must be an array of float64 (TArrayD)") + + tprofile2d = uproot.models.TH.Model_TProfile2D_v8.empty() + tprofile2d._bases.append(th2x) + tprofile2d._members["fBinEntries"] = tarray_fBinEntries + tprofile2d._members["fErrorMode"] = fErrorMode + tprofile2d._members["fZmin"] = fZmin + tprofile2d._members["fZmax"] = fZmax + tprofile2d._members["fTsumwz"] = fTsumwz + tprofile2d._members["fTsumwz2"] = fTsumwz2 + tprofile2d._members["fBinSumw2"] = tarray_fBinSumw2 + + tprofile2d._deeply_writable = th2x._deeply_writable + + return tprofile2d + + +def to_TProfile3D( + fName, + fTitle, + data, + fEntries, + fTsumw, + fTsumw2, + fTsumwx, + fTsumwx2, + fTsumwy, + fTsumwy2, + fTsumwxy, + fTsumwz, + fTsumwz2, + fTsumwxz, + fTsumwyz, + fTsumwt, + fTsumwt2, + fSumw2, + fBinEntries, + fBinSumw2, + fXaxis, + fYaxis, + fZaxis, + fTmin=0.0, + fTmax=0.0, + fErrorMode=0, + fNcells=None, + fBarOffset=0, + fBarWidth=1000, + fMaximum=-1111.0, + fMinimum=-1111.0, + fNormFactor=0.0, + fContour=None, + fOption="", + fFunctions=None, + fBufferSize=0, + fBuffer=None, + fBinStatErrOpt=0, + fStatOverflows=2, + fLineColor=602, + fLineStyle=1, + fLineWidth=1, + fFillColor=0, + fFillStyle=1001, + fMarkerColor=1, + fMarkerStyle=1, + fMarkerSize=1.0, +): + """ + Args: + fName (None or str): Temporary name, will be overwritten by the writing + process because Uproot's write syntax is ``file[name] = histogram``. + fTitle (str): Real title of the histogram. + data (numpy.ndarray or :doc:`uproot.models.TArray.Model_TArray`): Bin contents + with first bin as underflow, last bin as overflow. The dtype of this array + must be float64. + fEntries (float): Number of entries. (https://root.cern.ch/doc/master/classTH1.html) + fTsumw (float): Total Sum of weights. + fTsumw2 (float): Total Sum of squares of weights. + fTsumwx (float): Total Sum of weight*X. + fTsumwx2 (float): Total Sum of weight*X*X. + fTsumwy (float): Total Sum of weight*Y. (TH3 only: https://root.cern.ch/doc/master/classTH3.html) + fTsumwy2 (float): Total Sum of weight*Y*Y. (TH3 only.) + fTsumwxy (float): Total Sum of weight*X*Y. (TH3 only.) + fTsumwz (float): Total Sum of weight*Z. (TH3 only.) + fTsumwz2 (float): Total Sum of weight*Z*Z. (TH3 only.) + fTsumwxz (float): Total Sum of weight*X*Z. (TH3 only.) + fTsumwyz (float): Total Sum of weight*Y*Z. (TH3 only.) + fTsumwt (float): Total Sum of weight*T. (TProfile3D only: https://root.cern.ch/doc/master/classTProfile3D.html) + fTsumwt2 (float): Total Sum of weight*T*T. (TProfile3D only.) + fSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights. + fBinEntries (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Number + of entries per bin. (TProfile3D only.) + fBinSumw2 (numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + of sum of squares of weights per bin. (TProfile3D only.) + fXaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="xaxis"`` and ``fTitle=""``. + fYaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="yaxis"`` and ``fTitle=""``. + fZaxis (:doc:`uproot.models.TH.Model_TAxis_v10`): Use :doc:`uproot.writing.to_TAxis` + with ``fName="zaxis"`` and ``fTitle=""``. + fTmin (float): Lower limit in T (if set). (TProfile3D only.) + fTmax (float): Upper limit in T (if set). (TProfile3D only.) + fErrorMode (int): Option to compute errors. (TProfile3D only.) + fNcells (None or int): Number of bins(1D), cells (2D) +U/Overflows. Computed + from ``data`` if None. + fBarOffset (int): (1000*offset) for bar charts or legos + fBarWidth (int): (1000*width) for bar charts or legos + fMaximum (float): Maximum value for plotting. + fMinimum (float): Minimum value for plotting. + fNormFactor (float): Normalization factor. + fContour (None or numpy.ndarray of numpy.float64 or :doc:`uproot.models.TArray.Model_TArrayD`): Array + to display contour levels. None generates an empty array. + fOption (str or :doc:`uproot.models.TString.Model_TString`): Histogram options. + fFunctions (None, list, or :doc:`uproot.models.TList.Model_TList`): ->Pointer to + list of functions (fits and user). None generates an empty list. + fBufferSize (None or int): fBuffer size. Computed from ``fBuffer`` if None. + fBuffer (None or numpy.ndarray of numpy.float64): Buffer of entries accumulated + before automatically choosing the binning. (Irrelevant for serialization?) + None generates an empty array. + fBinStatErrOpt (int): Option for bin statistical errors. + fStatOverflows (int): Per object flag to use under/overflows in statistics. + fLineColor (int): Line color. (https://root.cern.ch/doc/master/classTAttLine.html) + fLineStyle (int): Line style. + fLineWidth (int): Line width. + fFillColor (int): Fill area color. (https://root.cern.ch/doc/master/classTAttFill.html) + fFillStyle (int): Fill area style. + fMarkerColor (int): Marker color. (https://root.cern.ch/doc/master/classTAttMarker.html) + fMarkerStyle (int): Marker style. + fMarkerSize (float): Marker size. + + This function is for developers to create TProfile3D objects that can be + written to ROOT files, to implement conversion routines. + """ + th3x = to_TH3x( + fName=fName, + fTitle=fTitle, + data=data, + fEntries=fEntries, + fTsumw=fTsumw, + fTsumw2=fTsumw2, + fTsumwx=fTsumwx, + fTsumwx2=fTsumwx2, + fTsumwy=fTsumwy, + fTsumwy2=fTsumwy2, + fTsumwxy=fTsumwxy, + fTsumwz=fTsumwz, + fTsumwz2=fTsumwz2, + fTsumwxz=fTsumwxz, + fTsumwyz=fTsumwyz, + fSumw2=fSumw2, + fXaxis=fXaxis, + fYaxis=fYaxis, + fZaxis=fZaxis, + fNcells=fNcells, + fBarOffset=fBarOffset, + fBarWidth=fBarWidth, + fMaximum=fMaximum, + fMinimum=fMinimum, + fNormFactor=fNormFactor, + fContour=fContour, + fOption=fOption, + fFunctions=fFunctions, + fBufferSize=fBufferSize, + fBuffer=fBuffer, + fBinStatErrOpt=fBinStatErrOpt, + fStatOverflows=fStatOverflows, + fLineColor=fLineColor, + fLineStyle=fLineStyle, + fLineWidth=fLineWidth, + fFillColor=fFillColor, + fFillStyle=fFillStyle, + fMarkerColor=fMarkerColor, + fMarkerStyle=fMarkerStyle, + fMarkerSize=fMarkerSize, + ) + if not isinstance(th3x, uproot.models.TH.Model_TH3D_v4): + raise TypeError("TProfile3D requires an array of float64 (TArrayD)") + + if isinstance(fBinEntries, uproot.models.TArray.Model_TArray): + tarray_fBinEntries = fBinEntries + else: + tarray_fBinEntries = to_TArray(fBinEntries) + if not isinstance(tarray_fBinEntries, uproot.models.TArray.Model_TArrayD): + raise TypeError("fBinEntries must be an array of float64 (TArrayD)") + + if isinstance(fBinSumw2, uproot.models.TArray.Model_TArray): + tarray_fBinSumw2 = fBinSumw2 + else: + tarray_fBinSumw2 = to_TArray(fBinSumw2) + if not isinstance(tarray_fBinSumw2, uproot.models.TArray.Model_TArrayD): + raise TypeError("fBinSumw2 must be an array of float64 (TArrayD)") + + tprofile3d = uproot.models.TH.Model_TProfile3D_v8.empty() + tprofile3d._bases.append(th3x) + tprofile3d._members["fBinEntries"] = tarray_fBinEntries + tprofile3d._members["fErrorMode"] = fErrorMode + tprofile3d._members["fTmin"] = fTmin + tprofile3d._members["fTmax"] = fTmax + tprofile3d._members["fTsumwt"] = fTsumwt + tprofile3d._members["fTsumwt2"] = fTsumwt2 + tprofile3d._members["fBinSumw2"] = tarray_fBinSumw2 + + tprofile3d._deeply_writable = th3x._deeply_writable + + return tprofile3d diff --git a/tests/test_0404-write-a-histogram.py b/tests/test_0404-write-a-histogram.py new file mode 100644 index 000000000..93baef5bc --- /dev/null +++ b/tests/test_0404-write-a-histogram.py @@ -0,0 +1,750 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/main/LICENSE + +import os + +import numpy as np +import pytest +import skhep_testdata + +import uproot +import uproot.writing + +ROOT = pytest.importorskip("ROOT") + + +def test_copy(tmp_path): + original = os.path.join(tmp_path, "original.root") + newfile = os.path.join(tmp_path, "newfile.root") + + f1 = ROOT.TFile(original, "recreate") + h1 = ROOT.TH1F("h1", "title", 8, -3.14, 2.71) + h1.SetBinContent(0, 0.0) + h1.SetBinContent(1, 1.1) + h1.SetBinContent(2, 2.2) + h1.SetBinContent(3, 3.3) + h1.SetBinContent(4, 4.4) + h1.SetBinContent(5, 5.5) + h1.SetBinContent(6, 6.6) + h1.SetBinContent(7, 7.7) + h1.SetBinContent(8, 8.8) + h1.SetBinContent(9, 9.9) + h1.Write() + f1.Close() + + with uproot.open(original) as fin: + h2 = fin["h1"] + + with uproot.recreate(newfile) as fout: + fout["h1"] = h2 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("h1") + assert h3.GetBinContent(0) == pytest.approx(0.0) + assert h3.GetBinContent(1) == pytest.approx(1.1) + assert h3.GetBinContent(2) == pytest.approx(2.2) + assert h3.GetBinContent(3) == pytest.approx(3.3) + assert h3.GetBinContent(4) == pytest.approx(4.4) + assert h3.GetBinContent(5) == pytest.approx(5.5) + assert h3.GetBinContent(6) == pytest.approx(6.6) + assert h3.GetBinContent(7) == pytest.approx(7.7) + assert h3.GetBinContent(8) == pytest.approx(8.8) + assert h3.GetBinContent(9) == pytest.approx(9.9) + + +def test_from_old(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + with uproot.open(skhep_testdata.data_path("uproot-histograms.root")) as fin: + one = fin["one"] + + with uproot.recreate(newfile) as fout: + fout["one"] = one + + f1 = ROOT.TFile(newfile) + h1 = f1.Get("one") + assert h1.GetBinContent(0) == 0 + assert h1.GetBinContent(1) == 68 + assert h1.GetBinContent(2) == 285 + assert h1.GetBinContent(3) == 755 + assert h1.GetBinContent(4) == 1580 + assert h1.GetBinContent(5) == 2296 + assert h1.GetBinContent(6) == 2286 + assert h1.GetBinContent(7) == 1570 + assert h1.GetBinContent(8) == 795 + assert h1.GetBinContent(9) == 289 + assert h1.GetBinContent(10) == 76 + assert h1.GetBinContent(11) == 0 + + +def test_new_name(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + with uproot.open(skhep_testdata.data_path("uproot-histograms.root")) as fin: + one = fin["one"] + + with uproot.recreate(newfile) as fout: + fout["whatever"] = one + + f1 = ROOT.TFile(newfile) + h1 = f1.Get("whatever") + assert h1.GetBinContent(0) == 0 + assert h1.GetBinContent(1) == 68 + assert h1.GetBinContent(2) == 285 + assert h1.GetBinContent(3) == 755 + assert h1.GetBinContent(4) == 1580 + assert h1.GetBinContent(5) == 2296 + assert h1.GetBinContent(6) == 2286 + assert h1.GetBinContent(7) == 1570 + assert h1.GetBinContent(8) == 795 + assert h1.GetBinContent(9) == 289 + assert h1.GetBinContent(10) == 76 + assert h1.GetBinContent(11) == 0 + + +@pytest.mark.parametrize("cls", [ROOT.TH1C, ROOT.TH1D, ROOT.TH1F, ROOT.TH1I, ROOT.TH1S]) +def test_all_TH1(tmp_path, cls): + original = os.path.join(tmp_path, "original.root") + newfile = os.path.join(tmp_path, "newfile.root") + + f1 = ROOT.TFile(original, "recreate") + h1 = cls("h1", "title", 2, -3.14, 2.71) + h1.Fill(-4) + h1.Fill(-3.1) + h1.Fill(-3.1) + h1.Fill(2.7, 5) + h1.Fill(3, 4) + h1.Write() + f1.Close() + + with uproot.open(original) as fin: + h2 = fin["h1"] + + with uproot.recreate(newfile) as fout: + fout["out"] = h2 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 7 + assert h3.GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetBinWidth(1) == pytest.approx((2.71 - -3.14) / 2) + assert h3.GetBinContent(0) == pytest.approx(1) + assert h3.GetBinContent(1) == pytest.approx(2) + assert h3.GetBinContent(2) == pytest.approx(5) + assert h3.GetBinContent(3) == pytest.approx(4) + assert h3.GetBinError(0) == pytest.approx(1) + assert h3.GetBinError(1) == pytest.approx(1.4142135623730951) + assert h3.GetBinError(2) == pytest.approx(5) + assert h3.GetBinError(3) == pytest.approx(4) + + +@pytest.mark.parametrize("cls", [ROOT.TH2C, ROOT.TH2D, ROOT.TH2F, ROOT.TH2I, ROOT.TH2S]) +def test_all_TH2(tmp_path, cls): + original = os.path.join(tmp_path, "original.root") + newfile = os.path.join(tmp_path, "newfile.root") + + f1 = ROOT.TFile(original, "recreate") + h1 = cls("h1", "title", 2, -3.14, 2.71, 3, -5, 10) + h1.Fill(-4, 9) + h1.Fill(-3.1, 9) + h1.Fill(-3.1, 9) + h1.Fill(2.7, -4, 5) + h1.Fill(3, 9, 4) + h1.Write() + f1.Close() + + with uproot.open(original) as fin: + h2 = fin["h1"] + + with uproot.recreate(newfile) as fout: + fout["out"] = h2 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 7 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert [[h3.GetBinContent(i, j) for j in range(5)] for i in range(4)] == [ + pytest.approx([0, 0, 0, 1, 0]), + pytest.approx([0, 0, 0, 2, 0]), + pytest.approx([0, 5, 0, 0, 0]), + pytest.approx([0, 0, 0, 4, 0]), + ] + + +@pytest.mark.parametrize("cls", [ROOT.TH3C, ROOT.TH3D, ROOT.TH3F, ROOT.TH3I, ROOT.TH3S]) +def test_all_TH3(tmp_path, cls): + original = os.path.join(tmp_path, "original.root") + newfile = os.path.join(tmp_path, "newfile.root") + + f1 = ROOT.TFile(original, "recreate") + h1 = cls("h1", "title", 2, -3.14, 2.71, 3, -5, 10, 1, 100, 200) + h1.Fill(-4, 9, 150) + h1.Fill(-3.1, 9, 150) + h1.Fill(-3.1, 9, 150) + h1.Fill(2.7, -4, 150, 5) + h1.Fill(3, 9, 150, 4) + h1.Write() + f1.Close() + + with uproot.open(original) as fin: + h2 = fin["h1"] + + with uproot.recreate(newfile) as fout: + fout["out"] = h2 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 7 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetNbinsZ() == 1 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert h3.GetZaxis().GetBinLowEdge(1) == pytest.approx(100) + assert h3.GetZaxis().GetBinUpEdge(1) == pytest.approx(200) + approx = pytest.approx + assert [ + [[h3.GetBinContent(i, j, k) for k in range(3)] for j in range(5)] + for i in range(4) + ] == [ + [[0, 0, 0], approx([0, 0, 0]), [0, 0, 0], approx([0, 1, 0]), [0, 0, 0]], + [[0, 0, 0], approx([0, 0, 0]), [0, 0, 0], approx([0, 2, 0]), [0, 0, 0]], + [[0, 0, 0], approx([0, 5, 0]), [0, 0, 0], approx([0, 0, 0]), [0, 0, 0]], + [[0, 0, 0], approx([0, 0, 0]), [0, 0, 0], approx([0, 4, 0]), [0, 0, 0]], + ] + + +def test_TProfile(tmp_path): + original = os.path.join(tmp_path, "original.root") + newfile = os.path.join(tmp_path, "newfile.root") + + f1 = ROOT.TFile(original, "recreate") + h1 = ROOT.TProfile("h1", "title", 2, -3.14, 2.71) + h1.Fill(-4, 10) + h1.Fill(-3.1, 10) + h1.Fill(-3.1, 20) + h1.Fill(2.7, 20) + h1.Fill(3, 20) + h1.Write() + f1.Close() + + with uproot.open(original) as fin: + h2 = fin["h1"] + + with uproot.recreate(newfile) as fout: + fout["out"] = h2 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 35 + assert h3.GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetBinWidth(1) == pytest.approx((2.71 - -3.14) / 2) + assert h3.GetBinContent(0) == pytest.approx(10) + assert h3.GetBinContent(1) == pytest.approx(15) + assert h3.GetBinContent(2) == pytest.approx(20) + assert h3.GetBinContent(3) == pytest.approx(20) + assert h3.GetBinError(0) == pytest.approx(0) + assert h3.GetBinError(1) == pytest.approx(np.sqrt(12.5)) + assert h3.GetBinError(2) == pytest.approx(0) + assert h3.GetBinError(3) == pytest.approx(0) + + +def test_TProfile2D(tmp_path): + original = os.path.join(tmp_path, "original.root") + newfile = os.path.join(tmp_path, "newfile.root") + + f1 = ROOT.TFile(original, "recreate") + h1 = ROOT.TProfile2D("h1", "title", 2, -3.14, 2.71, 3, -5, 10) + h1.Fill(-4, 9, 10) + h1.Fill(-3.1, 9, 10) + h1.Fill(-3.1, 9, 20) + h1.Fill(2.7, -4, 20) + h1.Fill(3, 9, 20) + h1.Write() + f1.Close() + + with uproot.open(original) as fin: + h2 = fin["h1"] + + with uproot.recreate(newfile) as fout: + fout["out"] = h2 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 35 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert [[h3.GetBinContent(i, j) for j in range(5)] for i in range(4)] == [ + pytest.approx([0, 0, 0, 10, 0]), + pytest.approx([0, 0, 0, 15, 0]), + pytest.approx([0, 20, 0, 0, 0]), + pytest.approx([0, 0, 0, 20, 0]), + ] + assert [[h3.GetBinError(i, j) for j in range(5)] for i in range(4)] == [ + pytest.approx([0, 0, 0, 0, 0]), + pytest.approx([0, 0, 0, np.sqrt(12.5), 0]), + pytest.approx([0, 0, 0, 0, 0]), + pytest.approx([0, 0, 0, 0, 0]), + ] + + +def test_TProfile3D(tmp_path): + original = os.path.join(tmp_path, "original.root") + newfile = os.path.join(tmp_path, "newfile.root") + + f1 = ROOT.TFile(original, "recreate") + h1 = ROOT.TProfile3D("h1", "title", 2, -3.14, 2.71, 3, -5, 10, 1, 100, 200) + h1.Fill(-4, 9, 150, 10) + h1.Fill(-3.1, 9, 150, 10) + h1.Fill(-3.1, 9, 150, 20) + h1.Fill(2.7, -4, 150, 20) + h1.Fill(3, 9, 150, 20) + h1.Write() + f1.Close() + + with uproot.open(original) as fin: + h2 = fin["h1"] + + with uproot.recreate(newfile) as fout: + fout["out"] = h2 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 35 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetNbinsZ() == 1 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert h3.GetZaxis().GetBinLowEdge(1) == pytest.approx(100) + assert h3.GetZaxis().GetBinUpEdge(1) == pytest.approx(200) + approx = pytest.approx + assert [ + [[h3.GetBinContent(i, j, k) for k in range(3)] for j in range(5)] + for i in range(4) + ] == [ + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 10, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 15, 0], [0, 0, 0]], + [[0, 0, 0], [0, 20, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 20, 0], [0, 0, 0]], + ] + assert [ + [[h3.GetBinError(i, j, k) for k in range(3)] for j in range(5)] + for i in range(4) + ] == [ + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, approx(np.sqrt(12.5)), 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + ] + + +def test_ex_nihilo_TH1(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h1 = uproot.writing.to_TH1x( + fName="h1", + fTitle="title", + data=np.array([1.0, 2.0, 5.0, 4.0], np.float64), + fEntries=5.0, + fTsumw=7.0, + fTsumw2=27.0, + fTsumwx=7.3, + fTsumwx2=55.67, + fSumw2=np.array([1.0, 2.0, 25.0, 16.0], np.float64), + fXaxis=uproot.writing.to_TAxis( + fName="xaxis", + fTitle="", + fNbins=2, + fXmin=-3.14, + fXmax=2.71, + ), + ) + + with uproot.recreate(newfile) as fout: + fout["out"] = h1 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 7 + assert h3.GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetBinWidth(1) == pytest.approx((2.71 - -3.14) / 2) + assert h3.GetBinContent(0) == pytest.approx(1) + assert h3.GetBinContent(1) == pytest.approx(2) + assert h3.GetBinContent(2) == pytest.approx(5) + assert h3.GetBinContent(3) == pytest.approx(4) + assert h3.GetBinError(0) == pytest.approx(1) + assert h3.GetBinError(1) == pytest.approx(1.4142135623730951) + assert h3.GetBinError(2) == pytest.approx(5) + assert h3.GetBinError(3) == pytest.approx(4) + + +def test_ex_nihilo_TH2(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h1 = uproot.writing.to_TH2x( + fName="h1", + fTitle="title", + data=np.array( + [0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 1, 2, 0, 4, 0, 0, 0, 0], np.float64 + ), + fEntries=5.0, + fTsumw=7.0, + fTsumw2=27.0, + fTsumwx=7.3, + fTsumwx2=55.67, + fTsumwy=-2.0, + fTsumwy2=242.0, + fTsumwxy=-109.8, + fSumw2=np.array( + [0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 1, 2, 0, 16, 0, 0, 0, 0], np.float64 + ), + fXaxis=uproot.writing.to_TAxis( + fName="xaxis", + fTitle="", + fNbins=2, + fXmin=-3.14, + fXmax=2.71, + ), + fYaxis=uproot.writing.to_TAxis( + fName="yaxis", + fTitle="", + fNbins=3, + fXmin=-5.0, + fXmax=10.0, + ), + ) + + with uproot.recreate(newfile) as fout: + fout["out"] = h1 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 7 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert [[h3.GetBinContent(i, j) for j in range(5)] for i in range(4)] == [ + pytest.approx([0, 0, 0, 1, 0]), + pytest.approx([0, 0, 0, 2, 0]), + pytest.approx([0, 5, 0, 0, 0]), + pytest.approx([0, 0, 0, 4, 0]), + ] + + +def test_ex_nihilo_TH3(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h1 = uproot.writing.to_TH3x( + fName="h1", + fTitle="title", + data=np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 1, 2, 0, 4, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + np.float64, + ), + fEntries=5.0, + fTsumw=7.0, + fTsumw2=27.0, + fTsumwx=7.3, + fTsumwx2=55.67, + fTsumwy=-2.0, + fTsumwy2=242.0, + fTsumwxy=-109.8, + fTsumwz=1050.0, + fTsumwz2=157500.0, + fTsumwxz=1095.0, + fTsumwyz=-300.0, + fSumw2=np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 1, 2, 0, 16, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + np.float64, + ), + fXaxis=uproot.writing.to_TAxis( + fName="xaxis", + fTitle="", + fNbins=2, + fXmin=-3.14, + fXmax=2.71, + ), + fYaxis=uproot.writing.to_TAxis( + fName="yaxis", + fTitle="", + fNbins=3, + fXmin=-5.0, + fXmax=10.0, + ), + fZaxis=uproot.writing.to_TAxis( + fName="zaxis", + fTitle="", + fNbins=1, + fXmin=100.0, + fXmax=200.0, + ), + ) + + with uproot.recreate(newfile) as fout: + fout["out"] = h1 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 7 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetNbinsZ() == 1 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert h3.GetZaxis().GetBinLowEdge(1) == pytest.approx(100) + assert h3.GetZaxis().GetBinUpEdge(1) == pytest.approx(200) + approx = pytest.approx + assert [ + [[h3.GetBinContent(i, j, k) for k in range(3)] for j in range(5)] + for i in range(4) + ] == [ + [[0, 0, 0], approx([0, 0, 0]), [0, 0, 0], approx([0, 1, 0]), [0, 0, 0]], + [[0, 0, 0], approx([0, 0, 0]), [0, 0, 0], approx([0, 2, 0]), [0, 0, 0]], + [[0, 0, 0], approx([0, 5, 0]), [0, 0, 0], approx([0, 0, 0]), [0, 0, 0]], + [[0, 0, 0], approx([0, 0, 0]), [0, 0, 0], approx([0, 4, 0]), [0, 0, 0]], + ] + + +def test_ex_nihilo_TProfile(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h1 = uproot.writing.to_TProfile( + fName="h1", + fTitle="title", + data=np.array([10, 30, 20, 20], np.float64), + fEntries=5.0, + fTsumw=3.0, + fTsumw2=3.0, + fTsumwx=-3.5, + fTsumwx2=26.51, + fTsumwy=50.0, + fTsumwy2=900.0, + fSumw2=np.array([100, 500, 400, 400], np.float64), + fBinEntries=np.array([1, 2, 1, 1], np.float64), + fBinSumw2=np.array([], np.float64), + fXaxis=uproot.writing.to_TAxis( + fName="xaxis", + fTitle="", + fNbins=2, + fXmin=-3.14, + fXmax=2.71, + ), + ) + + with uproot.recreate(newfile) as fout: + fout["out"] = h1 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 35 + assert h3.GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetBinWidth(1) == pytest.approx((2.71 - -3.14) / 2) + assert h3.GetBinContent(0) == pytest.approx(10) + assert h3.GetBinContent(1) == pytest.approx(15) + assert h3.GetBinContent(2) == pytest.approx(20) + assert h3.GetBinContent(3) == pytest.approx(20) + assert h3.GetBinError(0) == pytest.approx(0) + assert h3.GetBinError(1) == pytest.approx(np.sqrt(12.5)) + assert h3.GetBinError(2) == pytest.approx(0) + assert h3.GetBinError(3) == pytest.approx(0) + + +def test_ex_nihilo_TProfile2D(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h1 = uproot.writing.to_TProfile2D( + fName="h1", + fTitle="title", + data=np.array( + [0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 10, 30, 0, 20, 0, 0, 0, 0], np.float64 + ), + fEntries=5.0, + fTsumw=3.0, + fTsumw2=3.0, + fTsumwx=-3.5, + fTsumwx2=26.51, + fTsumwy=14.0, + fTsumwy2=178.0, + fTsumwxy=-66.6, + fTsumwz=50.0, + fTsumwz2=900.0, + fSumw2=np.array( + [0, 0, 0, 0, 0, 0, 400, 0, 0, 0, 0, 0, 100, 500, 0, 400, 0, 0, 0, 0], + np.float64, + ), + fBinEntries=np.array( + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 0, 0], np.float64 + ), + fBinSumw2=np.array([], np.float64), + fXaxis=uproot.writing.to_TAxis( + fName="xaxis", + fTitle="", + fNbins=2, + fXmin=-3.14, + fXmax=2.71, + ), + fYaxis=uproot.writing.to_TAxis( + fName="yaxis", + fTitle="", + fNbins=3, + fXmin=-5.0, + fXmax=10.0, + ), + ) + + with uproot.recreate(newfile) as fout: + fout["out"] = h1 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 35 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert [[h3.GetBinContent(i, j) for j in range(5)] for i in range(4)] == [ + pytest.approx([0, 0, 0, 10, 0]), + pytest.approx([0, 0, 0, 15, 0]), + pytest.approx([0, 20, 0, 0, 0]), + pytest.approx([0, 0, 0, 20, 0]), + ] + assert [[h3.GetBinError(i, j) for j in range(5)] for i in range(4)] == [ + pytest.approx([0, 0, 0, 0, 0]), + pytest.approx([0, 0, 0, np.sqrt(12.5), 0]), + pytest.approx([0, 0, 0, 0, 0]), + pytest.approx([0, 0, 0, 0, 0]), + ] + + +def test_ex_nihilo_TProfile3D(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h1 = uproot.writing.to_TProfile3D( + fName="h1", + fTitle="title", + data=np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 10, 30, 0, 20, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + np.float64, + ), + fEntries=5.0, + fTsumw=3.0, + fTsumw2=3.0, + fTsumwx=-3.5, + fTsumwx2=26.51, + fTsumwy=14.0, + fTsumwy2=178.0, + fTsumwxy=-66.6, + fTsumwz=450.0, + fTsumwz2=67500.0, + fTsumwxz=-525.0, + fTsumwyz=2100.0, + fTsumwt=50.0, + fTsumwt2=900.0, + fSumw2=np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 400, 0, 0, 0, 0, 0, 100, 500, 0, 400, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + np.float64, + ), + fBinEntries=np.array( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 0, 0] + + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + np.float64, + ), + fBinSumw2=np.array([], np.float64), + fXaxis=uproot.writing.to_TAxis( + fName="xaxis", + fTitle="", + fNbins=2, + fXmin=-3.14, + fXmax=2.71, + ), + fYaxis=uproot.writing.to_TAxis( + fName="yaxis", + fTitle="", + fNbins=3, + fXmin=-5.0, + fXmax=10.0, + ), + fZaxis=uproot.writing.to_TAxis( + fName="zaxis", + fTitle="", + fNbins=1, + fXmin=100.0, + fXmax=200.0, + ), + ) + + with uproot.recreate(newfile) as fout: + fout["out"] = h1 + + f3 = ROOT.TFile(newfile) + h3 = f3.Get("out") + assert h3.GetEntries() == 5 + assert h3.GetSumOfWeights() == 35 + assert h3.GetNbinsX() == 2 + assert h3.GetNbinsY() == 3 + assert h3.GetNbinsZ() == 1 + assert h3.GetXaxis().GetBinLowEdge(1) == pytest.approx(-3.14) + assert h3.GetXaxis().GetBinUpEdge(2) == pytest.approx(2.71) + assert h3.GetYaxis().GetBinLowEdge(1) == pytest.approx(-5) + assert h3.GetYaxis().GetBinUpEdge(3) == pytest.approx(10) + assert h3.GetZaxis().GetBinLowEdge(1) == pytest.approx(100) + assert h3.GetZaxis().GetBinUpEdge(1) == pytest.approx(200) + approx = pytest.approx + assert [ + [[h3.GetBinContent(i, j, k) for k in range(3)] for j in range(5)] + for i in range(4) + ] == [ + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 10, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 15, 0], [0, 0, 0]], + [[0, 0, 0], [0, 20, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 20, 0], [0, 0, 0]], + ] + assert [ + [[h3.GetBinError(i, j, k) for k in range(3)] for j in range(5)] + for i in range(4) + ] == [ + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, approx(np.sqrt(12.5)), 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + ]