udtools 0.2.1__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {udtools-0.2.1 → udtools-0.2.4}/PKG-INFO +19 -6
  2. {udtools-0.2.1 → udtools-0.2.4}/README.md +16 -2
  3. {udtools-0.2.1 → udtools-0.2.4}/pyproject.toml +6 -3
  4. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/__init__.py +1 -1
  5. udtools-0.2.1/src/udtools/eval.py → udtools-0.2.4/src/udtools/udeval.py +97 -22
  6. {udtools-0.2.1 → udtools-0.2.4}/src/udtools.egg-info/PKG-INFO +19 -6
  7. {udtools-0.2.1 → udtools-0.2.4}/src/udtools.egg-info/SOURCES.txt +1 -1
  8. {udtools-0.2.1 → udtools-0.2.4}/LICENSE.txt +0 -0
  9. {udtools-0.2.1 → udtools-0.2.4}/setup.cfg +0 -0
  10. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/argparser.py +0 -0
  11. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/cli.py +0 -0
  12. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/data.json +0 -0
  13. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/deprels.json +0 -0
  14. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/docdeps.json +0 -0
  15. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/docfeats.json +0 -0
  16. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/edeprels.json +0 -0
  17. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/feats.json +0 -0
  18. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/tospace.json +0 -0
  19. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/udeprels.json +0 -0
  20. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data/upos.json +0 -0
  21. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/data.py +0 -0
  22. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/incident.py +0 -0
  23. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/level1.py +0 -0
  24. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/level2.py +0 -0
  25. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/level3.py +0 -0
  26. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/level4.py +0 -0
  27. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/level5.py +0 -0
  28. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/level6.py +0 -0
  29. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/state.py +0 -0
  30. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/utils.py +0 -0
  31. {udtools-0.2.1 → udtools-0.2.4}/src/udtools/validator.py +0 -0
  32. {udtools-0.2.1 → udtools-0.2.4}/src/udtools.egg-info/dependency_links.txt +0 -0
  33. {udtools-0.2.1 → udtools-0.2.4}/src/udtools.egg-info/entry_points.txt +0 -0
  34. {udtools-0.2.1 → udtools-0.2.4}/src/udtools.egg-info/requires.txt +0 -0
  35. {udtools-0.2.1 → udtools-0.2.4}/src/udtools.egg-info/top_level.txt +0 -0
@@ -1,9 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: udtools
3
- Version: 0.2.1
3
+ Version: 0.2.4
4
4
  Summary: Python tools for Universal Dependencies
5
- Author: Filip Ginter, Sampo Pyysalo, Milan Straka, Martin Popel, Gosse Bouma
6
- Author-email: Daniel Zeman <zeman@ufal.mff.cuni.cz>
5
+ Author: Filip Ginter, Sampo Pyysalo, Daniel Zeman, Milan Straka, Martin Popel, Gosse Bouma, John Bauer, Arianna Masciolini, Ludovica Pannitto
7
6
  Maintainer-email: Daniel Zeman <zeman@ufal.mff.cuni.cz>
8
7
  License-Expression: GPL-2.0-or-later
9
8
  Project-URL: Homepage, https://universaldependencies.org/
@@ -12,7 +11,7 @@ Classifier: Development Status :: 2 - Pre-Alpha
12
11
  Classifier: Intended Audience :: Science/Research
13
12
  Classifier: Programming Language :: Python :: 3
14
13
  Classifier: Operating System :: OS Independent
15
- Requires-Python: >=3.11
14
+ Requires-Python: >=3.10
16
15
  Description-Content-Type: text/markdown
17
16
  License-File: LICENSE.txt
18
17
  Requires-Dist: udapi>=0.5.0
@@ -243,11 +242,25 @@ files must be valid at least at level 2, and their underlying text must be compa
243
242
  whitespace but not in other characters. The scorer evaluates similarity of the system output to the gold standard
244
243
  by computing several metrics that were defined in the UD parsing shared tasks (CoNLL 2017 & 2018, IWPT 2020 & 2021).
245
244
 
246
- If you supply `--help` as the only argument in the code below, you will get the description of the options available.
245
+ To load two files and evaluate their similarity without enhanced dependencies (i.e., in the style of the CoNLL shared
246
+ tasks), you can do the following.
247
+
248
+ ```python
249
+ from udtools.udeval import load_conllu_file, evaluate, build_evaluation_table
250
+
251
+ gold_ud = load_conllu_file('gold.conllu')
252
+ system_ud = load_conllu_file('system.conllu')
253
+ evaluation = evaluate(gold_ud, system_ud)
254
+ results = build_evaluation_table(evaluation, verbose=True)
255
+ print(results)
256
+ ```
257
+
258
+ To use the command line interface and arguments, you can use `parse_args_scorer()` as shown below. If you supply
259
+ `--help` as the only argument, you will get the description of the options available.
247
260
 
248
261
  ```python
249
262
  from udtools.argparser import parse_args_scorer
250
- from udtools.eval import evaluate_wrapper, build_evaluation_table
263
+ from udtools.udeval import evaluate_wrapper, build_evaluation_table
251
264
 
252
265
  args = parse_args_scorer()
253
266
  evaluation = evaluate_wrapper(args)
@@ -222,11 +222,25 @@ files must be valid at least at level 2, and their underlying text must be compa
222
222
  whitespace but not in other characters. The scorer evaluates similarity of the system output to the gold standard
223
223
  by computing several metrics that were defined in the UD parsing shared tasks (CoNLL 2017 & 2018, IWPT 2020 & 2021).
224
224
 
225
- If you supply `--help` as the only argument in the code below, you will get the description of the options available.
225
+ To load two files and evaluate their similarity without enhanced dependencies (i.e., in the style of the CoNLL shared
226
+ tasks), you can do the following.
227
+
228
+ ```python
229
+ from udtools.udeval import load_conllu_file, evaluate, build_evaluation_table
230
+
231
+ gold_ud = load_conllu_file('gold.conllu')
232
+ system_ud = load_conllu_file('system.conllu')
233
+ evaluation = evaluate(gold_ud, system_ud)
234
+ results = build_evaluation_table(evaluation, verbose=True)
235
+ print(results)
236
+ ```
237
+
238
+ To use the command line interface and arguments, you can use `parse_args_scorer()` as shown below. If you supply
239
+ `--help` as the only argument, you will get the description of the options available.
226
240
 
227
241
  ```python
228
242
  from udtools.argparser import parse_args_scorer
229
- from udtools.eval import evaluate_wrapper, build_evaluation_table
243
+ from udtools.udeval import evaluate_wrapper, build_evaluation_table
230
244
 
231
245
  args = parse_args_scorer()
232
246
  evaluation = evaluate_wrapper(args)
@@ -4,21 +4,24 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "udtools"
7
- version = "0.2.1"
7
+ version = "0.2.4"
8
8
  authors = [
9
9
  { name="Filip Ginter" },
10
10
  { name="Sampo Pyysalo" },
11
- { name="Daniel Zeman", email="zeman@ufal.mff.cuni.cz" },
11
+ { name="Daniel Zeman" },
12
12
  { name="Milan Straka" },
13
13
  { name="Martin Popel" },
14
14
  { name="Gosse Bouma" },
15
+ { name="John Bauer" },
16
+ { name="Arianna Masciolini" },
17
+ { name="Ludovica Pannitto" },
15
18
  ]
16
19
  maintainers = [
17
20
  { name="Daniel Zeman", email="zeman@ufal.mff.cuni.cz" },
18
21
  ]
19
22
  description = "Python tools for Universal Dependencies"
20
23
  readme = "README.md"
21
- requires-python = ">=3.11"
24
+ requires-python = ">=3.10"
22
25
  dependencies = ["udapi>=0.5.0", "regex>=2020.09.27"]
23
26
  classifiers = [
24
27
  "Development Status :: 2 - Pre-Alpha",
@@ -4,7 +4,7 @@ udtools.
4
4
  Python tools for Universal Dependencies.
5
5
  """
6
6
 
7
- __version__ = "0.2.1"
7
+ __version__ = "0.2.4"
8
8
  __author__ = 'Daniel Zeman'
9
9
  __credits__ = 'Universal Dependencies community'
10
10
 
@@ -133,12 +133,7 @@ UNIVERSAL_FEATURES = {
133
133
  class UDError(Exception):
134
134
  pass
135
135
 
136
- # Conversion methods handling `str` <-> `unicode` conversions in Python2
137
- def _decode(text):
138
- return text if sys.version_info[0] >= 3 or not isinstance(text, str) else text.decode("utf-8")
139
136
 
140
- def _encode(text):
141
- return text if sys.version_info[0] >= 3 or not isinstance(text, unicode) else text.encode("utf-8")
142
137
 
143
138
  CASE_DEPRELS = {'obl','nmod','conj','advcl'}
144
139
  UNIVERSAL_DEPREL_EXTENSIONS = {'pass','relcl','xsubj'}
@@ -216,7 +211,7 @@ def load_conllu(file, path, treebank_type):
216
211
  line_idx += 1 # errors will be displayed indexed from 1
217
212
  if not line:
218
213
  break
219
- line = _decode(line.rstrip("\r\n"))
214
+ line = line.rstrip("\r\n")
220
215
 
221
216
  # Handle sentence start boundaries
222
217
  if sentence_start is None:
@@ -234,7 +229,7 @@ def load_conllu(file, path, treebank_type):
234
229
  if word.parent is None:
235
230
  head = int(word.columns[HEAD])
236
231
  if head < 0 or head > len(ud.words) - sentence_start:
237
- raise UDError("HEAD '{}' points outside of the sentence that ends at line {}".format(_encode(word.columns[HEAD]), line_idx))
232
+ raise UDError("HEAD '{}' points outside of the sentence that ends at line {}".format(word.columns[HEAD], line_idx))
238
233
  if head:
239
234
  parent = ud.words[sentence_start + head - 1]
240
235
  word.parent = "remapping"
@@ -254,7 +249,7 @@ def load_conllu(file, path, treebank_type):
254
249
  # with preprocessing.
255
250
  if '.' in head:
256
251
  if treebank_type.get('no_empty_nodes', False):
257
- raise UDError("The collapsed CoNLL-U file still contains references to empty nodes at line {}: {}".format(line_idx, _encode(line)))
252
+ raise UDError("The collapsed CoNLL-U file still contains references to empty nodes at line {}: {}".format(line_idx, line))
258
253
  else:
259
254
  continue
260
255
  hd = int(head)
@@ -367,7 +362,7 @@ def load_conllu(file, path, treebank_type):
367
362
  # Read next token/word
368
363
  columns = line.split("\t")
369
364
  if len(columns) != 10:
370
- raise UDError("The CoNLL-U line does not contain 10 tab-separated columns at line {}: '{}'".format(line_idx, _encode(line)))
365
+ raise UDError("The CoNLL-U line does not contain 10 tab-separated columns at line {}: '{}'".format(line_idx, line))
371
366
 
372
367
  # Skip empty nodes
373
368
  # If we are evaluating enhanced graphs, empty nodes should have been collapsed
@@ -377,7 +372,7 @@ def load_conllu(file, path, treebank_type):
377
372
  if "." in columns[ID]:
378
373
  # When launching this script, we can specify that empty nodes should be considered errors.
379
374
  if treebank_type.get('no_empty_nodes', False):
380
- raise UDError("The collapsed CoNLL-U line still contains empty nodes at line {}: {}".format(line_idx, _encode(line)))
375
+ raise UDError("The collapsed CoNLL-U line still contains empty nodes at line {}: {}".format(line_idx, line))
381
376
  else:
382
377
  continue
383
378
 
@@ -398,21 +393,21 @@ def load_conllu(file, path, treebank_type):
398
393
  try:
399
394
  start, end = map(int, columns[ID].split("-"))
400
395
  except:
401
- raise UDError("Cannot parse multi-word token ID '{}' at line {}".format(_encode(columns[ID]), line_idx))
396
+ raise UDError("Cannot parse multi-word token ID '{}' at line {}".format(columns[ID], line_idx))
402
397
 
403
398
  words_expected = end - start + 1
404
399
  words_found = 0
405
400
  while words_found < words_expected:
406
- word_line = _decode(file.readline().rstrip("\r\n"))
401
+ word_line = file.readline().rstrip("\r\n")
407
402
  if not word_line:
408
403
  raise UDError("The CoNLL-U file ends in an unfinished MWT at line {}".format(line_idx))
409
404
  line_idx += 1
410
405
  word_columns = word_line.split("\t")
411
406
  if len(word_columns) != 10:
412
- raise UDError("The CoNLL-U line does not contain 10 tab-separated columns at line {}: '{}'".format(line_idx, _encode(word_line)))
407
+ raise UDError("The CoNLL-U line does not contain 10 tab-separated columns at line {}: '{}'".format(line_idx, word_line))
413
408
  if "." in word_columns[ID]:
414
409
  if treebank_type.get('no_empty_nodes', False):
415
- raise UDError("The collapsed CoNLL-U line still contains empty nodes at line {}: {}".format(line_idx, _encode(line)))
410
+ raise UDError("The collapsed CoNLL-U line still contains empty nodes at line {}: {}".format(line_idx, line))
416
411
  else:
417
412
  continue
418
413
  ud.words.append(UDWord(ud.tokens[-1], word_columns, is_multiword=True))
@@ -423,15 +418,15 @@ def load_conllu(file, path, treebank_type):
423
418
  try:
424
419
  word_id = int(columns[ID])
425
420
  except:
426
- raise UDError("Cannot parse word ID '{}' at line {}".format(_encode(columns[ID]), line_idx))
421
+ raise UDError("Cannot parse word ID '{}' at line {}".format(columns[ID], line_idx))
427
422
  if word_id != len(ud.words) - sentence_start + 1:
428
423
  raise UDError("Incorrect word ID '{}' for word '{}', expected '{}' at line {}".format(
429
- _encode(columns[ID]), _encode(columns[FORM]), len(ud.words) - sentence_start + 1, line_idx))
424
+ columns[ID], columns[FORM], len(ud.words) - sentence_start + 1, line_idx))
430
425
 
431
426
  try:
432
427
  head_id = int(columns[HEAD])
433
428
  except ValueError as e:
434
- raise UDError("Cannot parse HEAD '{}' at line {}".format(_encode(columns[HEAD]), line_idx)) from e
429
+ raise UDError("Cannot parse HEAD '{}' at line {}".format(columns[HEAD], line_idx)) from e
435
430
  if head_id < 0:
436
431
  raise UDError("HEAD cannot be negative at line %d" % line_idx)
437
432
 
@@ -442,8 +437,31 @@ def load_conllu(file, path, treebank_type):
442
437
 
443
438
  return ud
444
439
 
440
+
441
+
445
442
  # Evaluate the gold and system treebanks (loaded using load_conllu).
446
443
  def evaluate(gold_ud, system_ud):
444
+ """
445
+ Takes internal representations of two CoNLL-U files, compares their
446
+ contents and returns the scores.
447
+
448
+ Parameters
449
+ ----------
450
+ gold_ud : UDRepresentation
451
+ Gold standard data.
452
+ system_ud : UDRepresentationi
453
+ System output data.
454
+
455
+ Raises
456
+ ------
457
+ UDError
458
+ If the underlying texts of the files are not compatible.
459
+
460
+ Returns
461
+ -------
462
+ dict
463
+ Indexed by metric names, the values are scores.
464
+ """
447
465
  class Score:
448
466
  def __init__(self, gold_total, system_total, correct, aligned_total=None):
449
467
  self.correct = correct
@@ -657,8 +675,8 @@ def evaluate(gold_ud, system_ud):
657
675
  raise UDError(
658
676
  "The concatenation of tokens in gold file and in system file differ!\n" + gtokenreport + stokenreport +
659
677
  "First 20 differing characters in gold file: '{}' and system file: '{}'".format(
660
- "".join(map(_encode, gold_ud.characters[index:index + 20])),
661
- "".join(map(_encode, system_ud.characters[index:index + 20]))
678
+ "".join(gold_ud.characters[index:index + 20]),
679
+ "".join(system_ud.characters[index:index + 20])
662
680
  )
663
681
  )
664
682
 
@@ -690,13 +708,47 @@ def evaluate(gold_ud, system_ud):
690
708
  filter_fn=lambda w: w.is_content_deprel),
691
709
  }
692
710
 
711
+
712
+
693
713
  def load_conllu_file(path, treebank_type=None):
714
+ """
715
+ Reads a CoNLL-U file into internal representation.
716
+
717
+ Parameters
718
+ ----------
719
+ path : str
720
+ The name of (and path to) the file.
721
+ treebank_type : dict, optional
722
+ Additional information about what we expect / should read. The default is None.
723
+
724
+ Returns
725
+ -------
726
+ UDRepresentation
727
+ The internal representation of the file contents, usable in evaluate().
728
+ """
694
729
  if treebank_type is None:
695
730
  treebank_type = {}
696
- _file = open(path, mode="r", **({"encoding": "utf-8"} if sys.version_info >= (3, 0) else {}))
731
+ _file = open(path, mode="r", **({"encoding": "utf-8"}))
697
732
  return load_conllu(_file, path, treebank_type)
698
733
 
734
+
735
+
699
736
  def evaluate_wrapper(args):
737
+ """
738
+ Takes file names and options from command line arguments, loads the files,
739
+ evaluates their similarity and returns the result of evaluate(). Use
740
+ `--help` to obtain their description (or see udtools.argparser.parse_args_scorer()).
741
+
742
+ Parameters
743
+ ----------
744
+ args : argparse.Namespace
745
+ Command line arguments of the eval.py script.
746
+
747
+ Returns
748
+ -------
749
+ dict
750
+ Indexed by metric names, values are scores.
751
+ """
700
752
  treebank_type = {}
701
753
  enhancements = list(args.enhancements)
702
754
  treebank_type['no_gapping'] = 1 if '1' in enhancements else 0
@@ -713,7 +765,30 @@ def evaluate_wrapper(args):
713
765
  system_ud = load_conllu_file(args.system_file, treebank_type)
714
766
  return evaluate(gold_ud, system_ud)
715
767
 
716
- def build_evaluation_table(evaluation, verbose, counts, enhanced):
768
+
769
+
770
+ def build_evaluation_table(evaluation, verbose=True, counts=False, enhanced=False):
771
+ """
772
+ Creates a plaintext table with the results.
773
+
774
+ Parameters
775
+ ----------
776
+ evaluation : dict
777
+ The output of the evaluate() function.
778
+ verbose : bool, optional
779
+ Print results of all metrics. (Otherwise, print only LAS P+R+F1.)
780
+ Default is True.
781
+ counts : bool, optional
782
+ Print raw counts of correct/gold/system/aligned words instead of
783
+ precision/recall/F1 for all metrics. Default is False.
784
+ enhanced : bool, optional
785
+ Include evaluation of enhanced graphs. Default is False.
786
+
787
+ Returns
788
+ -------
789
+ str
790
+ The table with results.
791
+ """
717
792
  text = []
718
793
 
719
794
  # Print the evaluation
@@ -755,7 +830,7 @@ def build_evaluation_table(evaluation, verbose, counts, enhanced):
755
830
 
756
831
 
757
832
 
758
- # Tests, which can be executed with `python -m unittest eval`.
833
+ # Tests, which can be executed with `python -m unittest udeval`.
759
834
  class TestAlignment(unittest.TestCase):
760
835
  @staticmethod
761
836
  def _load_words(words):
@@ -1,9 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: udtools
3
- Version: 0.2.1
3
+ Version: 0.2.4
4
4
  Summary: Python tools for Universal Dependencies
5
- Author: Filip Ginter, Sampo Pyysalo, Milan Straka, Martin Popel, Gosse Bouma
6
- Author-email: Daniel Zeman <zeman@ufal.mff.cuni.cz>
5
+ Author: Filip Ginter, Sampo Pyysalo, Daniel Zeman, Milan Straka, Martin Popel, Gosse Bouma, John Bauer, Arianna Masciolini, Ludovica Pannitto
7
6
  Maintainer-email: Daniel Zeman <zeman@ufal.mff.cuni.cz>
8
7
  License-Expression: GPL-2.0-or-later
9
8
  Project-URL: Homepage, https://universaldependencies.org/
@@ -12,7 +11,7 @@ Classifier: Development Status :: 2 - Pre-Alpha
12
11
  Classifier: Intended Audience :: Science/Research
13
12
  Classifier: Programming Language :: Python :: 3
14
13
  Classifier: Operating System :: OS Independent
15
- Requires-Python: >=3.11
14
+ Requires-Python: >=3.10
16
15
  Description-Content-Type: text/markdown
17
16
  License-File: LICENSE.txt
18
17
  Requires-Dist: udapi>=0.5.0
@@ -243,11 +242,25 @@ files must be valid at least at level 2, and their underlying text must be compa
243
242
  whitespace but not in other characters. The scorer evaluates similarity of the system output to the gold standard
244
243
  by computing several metrics that were defined in the UD parsing shared tasks (CoNLL 2017 & 2018, IWPT 2020 & 2021).
245
244
 
246
- If you supply `--help` as the only argument in the code below, you will get the description of the options available.
245
+ To load two files and evaluate their similarity without enhanced dependencies (i.e., in the style of the CoNLL shared
246
+ tasks), you can do the following.
247
+
248
+ ```python
249
+ from udtools.udeval import load_conllu_file, evaluate, build_evaluation_table
250
+
251
+ gold_ud = load_conllu_file('gold.conllu')
252
+ system_ud = load_conllu_file('system.conllu')
253
+ evaluation = evaluate(gold_ud, system_ud)
254
+ results = build_evaluation_table(evaluation, verbose=True)
255
+ print(results)
256
+ ```
257
+
258
+ To use the command line interface and arguments, you can use `parse_args_scorer()` as shown below. If you supply
259
+ `--help` as the only argument, you will get the description of the options available.
247
260
 
248
261
  ```python
249
262
  from udtools.argparser import parse_args_scorer
250
- from udtools.eval import evaluate_wrapper, build_evaluation_table
263
+ from udtools.udeval import evaluate_wrapper, build_evaluation_table
251
264
 
252
265
  args = parse_args_scorer()
253
266
  evaluation = evaluate_wrapper(args)
@@ -5,7 +5,6 @@ src/udtools/__init__.py
5
5
  src/udtools/argparser.py
6
6
  src/udtools/cli.py
7
7
  src/udtools/data.py
8
- src/udtools/eval.py
9
8
  src/udtools/incident.py
10
9
  src/udtools/level1.py
11
10
  src/udtools/level2.py
@@ -14,6 +13,7 @@ src/udtools/level4.py
14
13
  src/udtools/level5.py
15
14
  src/udtools/level6.py
16
15
  src/udtools/state.py
16
+ src/udtools/udeval.py
17
17
  src/udtools/utils.py
18
18
  src/udtools/validator.py
19
19
  src/udtools.egg-info/PKG-INFO
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes