procfunc 0.36.0__tar.gz → 0.36.1__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 (111) hide show
  1. {procfunc-0.36.0/src/procfunc.egg-info → procfunc-0.36.1}/PKG-INFO +1 -1
  2. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/__init__.py +1 -1
  3. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/codegen/codegen.py +18 -14
  4. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/random.py +17 -17
  5. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/tracer/trace.py +5 -6
  6. {procfunc-0.36.0 → procfunc-0.36.1/src/procfunc.egg-info}/PKG-INFO +1 -1
  7. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc.egg-info/SOURCES.txt +1 -0
  8. procfunc-0.36.1/tests/test_random_mutation.py +92 -0
  9. {procfunc-0.36.0 → procfunc-0.36.1}/LICENSE.md +0 -0
  10. {procfunc-0.36.0 → procfunc-0.36.1}/README.md +0 -0
  11. {procfunc-0.36.0 → procfunc-0.36.1}/pyproject.toml +0 -0
  12. {procfunc-0.36.0 → procfunc-0.36.1}/setup.cfg +0 -0
  13. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/cli/__init__.py +0 -0
  14. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/cli/main.py +0 -0
  15. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/codegen/__init__.py +0 -0
  16. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/codegen/identifiers.py +0 -0
  17. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/codegen/repr.py +0 -0
  18. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/color.py +0 -0
  19. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/compute_graph/__init__.py +0 -0
  20. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/compute_graph/compute_graph.py +0 -0
  21. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/compute_graph/node.py +0 -0
  22. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/compute_graph/operators_info.py +0 -0
  23. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/compute_graph/proxy.py +0 -0
  24. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/compute_graph/util.py +0 -0
  25. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/context.py +0 -0
  26. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/control/__init__.py +0 -0
  27. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/__init__.py +0 -0
  28. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/color.py +0 -0
  29. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/compositor.py +0 -0
  30. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/construct_nodes.py +0 -0
  31. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/construct_operator.py +0 -0
  32. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/construct_special_cases.py +0 -0
  33. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/construct_standard.py +0 -0
  34. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/execute.py +0 -0
  35. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/infer_runtime_data_type.py +0 -0
  36. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/realize.py +0 -0
  37. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/execute/util.py +0 -0
  38. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/func.py +0 -0
  39. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/geo.py +0 -0
  40. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/manifest.json +0 -0
  41. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/math.py +0 -0
  42. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/shader.py +0 -0
  43. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/texture.py +0 -0
  44. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/types.py +0 -0
  45. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/util/__init__.py +0 -0
  46. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/util/bindings_util.py +0 -0
  47. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/util/bpy_node_info.py +0 -0
  48. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/nodes/util/node_function.py +0 -0
  49. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/__init__.py +0 -0
  50. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/_util.py +0 -0
  51. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/addons.py +0 -0
  52. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/attr.py +0 -0
  53. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/collection.py +0 -0
  54. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/curve.py +0 -0
  55. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/file.py +0 -0
  56. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/manifest.json +0 -0
  57. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/mesh.py +0 -0
  58. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/modifier.py +0 -0
  59. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/object.py +0 -0
  60. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/primitives/__init__.py +0 -0
  61. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/primitives/camera.py +0 -0
  62. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/primitives/curve.py +0 -0
  63. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/primitives/light.py +0 -0
  64. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/primitives/mesh.py +0 -0
  65. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/ops/uv.py +0 -0
  66. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/tracer/__init__.py +0 -0
  67. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/tracer/decorator.py +0 -0
  68. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/tracer/patch.py +0 -0
  69. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/tracer/proxy.py +0 -0
  70. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/__init__.py +0 -0
  71. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/cleanup.py +0 -0
  72. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/convert.py +0 -0
  73. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/distribution.py +0 -0
  74. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/extract_materials.py +0 -0
  75. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/infer_distribution.py +0 -0
  76. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/parameters.py +0 -0
  77. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transforms/util.py +0 -0
  78. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transpiler/__init__.py +0 -0
  79. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transpiler/bpy_to_computegraph.py +0 -0
  80. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transpiler/main.py +0 -0
  81. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transpiler/parse_attrs.py +0 -0
  82. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transpiler/parse_default_values.py +0 -0
  83. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/transpiler/parse_special_cases.py +0 -0
  84. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/types.py +0 -0
  85. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/__init__.py +0 -0
  86. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/bpy_data.py +0 -0
  87. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/bpy_info.py +0 -0
  88. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/camera.py +0 -0
  89. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/keyframe.py +0 -0
  90. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/log.py +0 -0
  91. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/manifest.py +0 -0
  92. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/pytree.py +0 -0
  93. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc/util/teardown.py +0 -0
  94. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc.egg-info/dependency_links.txt +0 -0
  95. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc.egg-info/entry_points.txt +0 -0
  96. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc.egg-info/requires.txt +0 -0
  97. {procfunc-0.36.0 → procfunc-0.36.1}/src/procfunc.egg-info/top_level.txt +0 -0
  98. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_asset.py +0 -0
  99. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_bpy_data_cleanup.py +0 -0
  100. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_cli_transpile.py +0 -0
  101. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_codegen.py +0 -0
  102. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_codegen_matrix.py +0 -0
  103. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_compute_graph.py +0 -0
  104. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_control.py +0 -0
  105. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_node_function.py +0 -0
  106. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_ops.py +0 -0
  107. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_pytree.py +0 -0
  108. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_random.py +0 -0
  109. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_teardown.py +0 -0
  110. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_trace.py +0 -0
  111. {procfunc-0.36.0 → procfunc-0.36.1}/tests/test_transforms.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: procfunc
3
- Version: 0.36.0
3
+ Version: 0.36.1
4
4
  Summary: Function-Oriented Abstractions for Procedural 3D Generation in Python
5
5
  License-Expression: BSD-3-Clause
6
6
  Project-URL: Homepage, https://github.com/princeton-vl/procfunc
@@ -2,7 +2,7 @@
2
2
  # ensure this gets imported first so that mathutils etc is available even if later modules dont import bpy
3
3
  import bpy
4
4
 
5
- __version__ = "0.36.0"
5
+ __version__ = "0.36.1"
6
6
 
7
7
  from numpy.random import Generator as RNG
8
8
 
@@ -606,7 +606,8 @@ def _codegen_for_assignment(
606
606
  def _expressions_scope_for_graph(
607
607
  graph: cg.ComputeGraph,
608
608
  scope_expressions: dict[int, str | list[str]],
609
- ) -> tuple[dict[int, str | list[str]], dict[int, bool]]:
609
+ fold_map: dict[int, bool],
610
+ ) -> dict[int, str | list[str]]:
610
611
  expressions: dict[int, str | list[str]] = {
611
612
  **scope_expressions.copy(),
612
613
  **_check_graph_input_names(graph, scope_expressions),
@@ -615,9 +616,6 @@ def _expressions_scope_for_graph(
615
616
  # when we want to refer to a value, what string should we insert?
616
617
  # - for most nodes: refer to a variable name
617
618
  # - for inlined expressions: emplace a expression string
618
- usages = cg.usages_per_node(graph)
619
- fold_map = _expression_fold_map(graph, expressions, usages=usages)
620
-
621
619
  node_names = identifiers.nodenames_from_fixed_and_infill(
622
620
  graph,
623
621
  fold_map=fold_map,
@@ -630,7 +628,7 @@ def _expressions_scope_for_graph(
630
628
  raise ValueError(f"Scope and node names had overlap: {intersection=}")
631
629
  expressions.update(node_names)
632
630
 
633
- return expressions, fold_map
631
+ return expressions
634
632
 
635
633
 
636
634
  def _codegen_for_graph(
@@ -646,17 +644,13 @@ def _codegen_for_graph(
646
644
  if add_version_comment:
647
645
  code_lines.append(f"# Code generated by procfunc v{pf.__version__}")
648
646
 
649
- expressions, fold_map = _expressions_scope_for_graph(graph, scope_expressions)
647
+ usages = cg.usages_per_node(graph)
648
+ fold_map = _expression_fold_map(graph, scope_expressions, usages)
649
+ expressions = _expressions_scope_for_graph(graph, scope_expressions, fold_map)
650
650
  _input_ids = set(id(node) for node in graph.inputs.values()) # noqa: F841
651
-
651
+ output_ids = {id(node) for node in graph.outputs.values()}
652
652
  last_varname: str = ""
653
653
 
654
- # Collect mutator call nodes so they emit as bare statements (no assignment)
655
- mutator_call_ids = set()
656
- for node in cg.traverse_depth_first(graph):
657
- if isinstance(node, cg.MutatedArgumentNode):
658
- mutator_call_ids.add(id(node.args[1]))
659
-
660
654
  for node in cg.traverse_depth_first(graph):
661
655
  if isinstance(node, cg.InputPlaceholderNode):
662
656
  continue # arguments are defined in _codegen_graph_inputs
@@ -673,7 +667,17 @@ def _codegen_for_graph(
673
667
  expressions[id(node)] = node_code
674
668
  continue
675
669
 
676
- if id(node) in mutator_call_ids:
670
+ node_usages = usages.get(id(node), [])
671
+ mutation_usages = [
672
+ usage
673
+ for usage in node_usages
674
+ if isinstance(usage, cg.MutatedArgumentNode) and usage.args[1] is node
675
+ ]
676
+ if (
677
+ mutation_usages
678
+ and mutation_usages == node_usages
679
+ and id(node) not in output_ids
680
+ ):
677
681
  code_lines.extend(node_code if isinstance(node_code, list) else [node_code])
678
682
  continue
679
683
 
@@ -10,7 +10,7 @@ T = TypeVar("T")
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
- @pf.tracer.primitive(allow_exec=True)
13
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
14
14
  def uniform(
15
15
  rng: np.random.Generator,
16
16
  low: float,
@@ -19,7 +19,7 @@ def uniform(
19
19
  return rng.uniform(low, high)
20
20
 
21
21
 
22
- @pf.tracer.primitive(allow_exec=True)
22
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
23
23
  def normal(
24
24
  rng: np.random.Generator,
25
25
  mean: float,
@@ -28,7 +28,7 @@ def normal(
28
28
  return rng.normal(mean, std)
29
29
 
30
30
 
31
- @pf.tracer.primitive(allow_exec=True)
31
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
32
32
  def randint(
33
33
  rng: np.random.Generator,
34
34
  low: int,
@@ -37,7 +37,7 @@ def randint(
37
37
  return rng.integers(low, high)
38
38
 
39
39
 
40
- @pf.tracer.primitive(allow_exec=True)
40
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
41
41
  def uniform_tails(
42
42
  rng: np.random.Generator,
43
43
  low: float,
@@ -50,7 +50,7 @@ def uniform_tails(
50
50
  return rng.uniform(high - (high - low) * tail_pct, high)
51
51
 
52
52
 
53
- @pf.tracer.primitive(allow_exec=True)
53
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
54
54
  def clip_gaussian(
55
55
  rng: np.random.Generator,
56
56
  mean: float,
@@ -75,7 +75,7 @@ def clip_gaussian(
75
75
  )
76
76
 
77
77
 
78
- @pf.tracer.primitive(allow_exec=True)
78
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
79
79
  def wrap_gaussian(
80
80
  rng: np.random.Generator,
81
81
  mean: float,
@@ -93,7 +93,7 @@ def wrap_gaussian(
93
93
  return x
94
94
 
95
95
 
96
- @pf.tracer.primitive(allow_exec=True)
96
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
97
97
  def exponential(
98
98
  rng: np.random.Generator,
99
99
  scale: float,
@@ -101,7 +101,7 @@ def exponential(
101
101
  return rng.exponential(scale)
102
102
 
103
103
 
104
- @pf.tracer.primitive(allow_exec=True)
104
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
105
105
  def log_uniform(
106
106
  rng: np.random.Generator,
107
107
  low: float,
@@ -111,7 +111,7 @@ def log_uniform(
111
111
  return np.exp(rng.uniform(np.log(low), np.log(high), size=size))
112
112
 
113
113
 
114
- @pf.tracer.primitive(allow_exec=True)
114
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
115
115
  def log_normal(
116
116
  rng: np.random.Generator,
117
117
  mean: float,
@@ -121,7 +121,7 @@ def log_normal(
121
121
  return np.exp(rng.normal(np.log(mean), std, size=size))
122
122
 
123
123
 
124
- @pf.tracer.primitive(allow_exec=True)
124
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
125
125
  def spherical_sample(
126
126
  rng: np.random.Generator,
127
127
  min_elevation: float | None = None,
@@ -142,7 +142,7 @@ def spherical_sample(
142
142
  )
143
143
 
144
144
 
145
- @pf.tracer.primitive(allow_exec=True)
145
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
146
146
  def mixture_of_gaussian(
147
147
  rng: np.random.Generator,
148
148
  means: np.ndarray,
@@ -167,7 +167,7 @@ def mixture_of_gaussian(
167
167
  return res
168
168
 
169
169
 
170
- @pf.tracer.primitive(allow_exec=True)
170
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
171
171
  def beta(
172
172
  rng: np.random.Generator,
173
173
  a: float,
@@ -176,7 +176,7 @@ def beta(
176
176
  return rng.beta(a, b)
177
177
 
178
178
 
179
- @pf.tracer.primitive(allow_exec=True)
179
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
180
180
  def poisson(
181
181
  rng: np.random.Generator,
182
182
  lam: float,
@@ -184,7 +184,7 @@ def poisson(
184
184
  return rng.poisson(lam)
185
185
 
186
186
 
187
- @pf.tracer.primitive(allow_exec=True)
187
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
188
188
  def triangular(
189
189
  rng: np.random.Generator,
190
190
  low: float,
@@ -194,7 +194,7 @@ def triangular(
194
194
  return rng.triangular(low, mode, high)
195
195
 
196
196
 
197
- @pf.tracer.primitive(allow_exec=True)
197
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
198
198
  def gamma(
199
199
  rng: np.random.Generator,
200
200
  shape: float,
@@ -203,7 +203,7 @@ def gamma(
203
203
  return rng.gamma(shape, scale)
204
204
 
205
205
 
206
- @pf.tracer.primitive(allow_exec=True)
206
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
207
207
  def binomial(
208
208
  rng: np.random.Generator,
209
209
  n: int,
@@ -212,7 +212,7 @@ def binomial(
212
212
  return rng.binomial(n, p)
213
213
 
214
214
 
215
- @pf.tracer.primitive(allow_exec=True)
215
+ @pf.tracer.primitive(allow_exec=True, mutates=["rng"])
216
216
  def geometric(
217
217
  rng: np.random.Generator,
218
218
  p: float,
@@ -181,6 +181,11 @@ def trace(
181
181
  name = func.__name__
182
182
 
183
183
  proxy_args = _map_args(func, **inputs)
184
+ input_nodes = {
185
+ k: v.node
186
+ for k, v in proxy_args.items()
187
+ if isinstance(v, cg.Proxy) and isinstance(v.node, cg.InputPlaceholderNode)
188
+ }
184
189
 
185
190
  if pf.context.globals.current_trace_level is not None:
186
191
  # TODO we can lift this restriction fairly(?) easily by having a global patcher & saving/restoring this state
@@ -222,12 +227,6 @@ def trace(
222
227
  outputs = pytree.PyTree(func_result)
223
228
  outputs = outputs.map(extract_node)
224
229
 
225
- input_nodes = {
226
- k: v.node
227
- for k, v in proxy_args.items()
228
- if isinstance(v, cg.Proxy) and isinstance(v.node, cg.InputPlaceholderNode)
229
- }
230
-
231
230
  compgraph = cg.ComputeGraph(
232
231
  inputs=pytree.PyTree(input_nodes),
233
232
  outputs=outputs,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: procfunc
3
- Version: 0.36.0
3
+ Version: 0.36.1
4
4
  Summary: Function-Oriented Abstractions for Procedural 3D Generation in Python
5
5
  License-Expression: BSD-3-Clause
6
6
  Project-URL: Homepage, https://github.com/princeton-vl/procfunc
@@ -103,6 +103,7 @@ tests/test_node_function.py
103
103
  tests/test_ops.py
104
104
  tests/test_pytree.py
105
105
  tests/test_random.py
106
+ tests/test_random_mutation.py
106
107
  tests/test_teardown.py
107
108
  tests/test_trace.py
108
109
  tests/test_transforms.py
@@ -0,0 +1,92 @@
1
+ import ast
2
+ from collections.abc import Callable
3
+
4
+ import numpy as np
5
+ import pytest
6
+
7
+ import procfunc as pf
8
+ from procfunc.codegen import codegen
9
+
10
+ RANDOM_CALLS = [
11
+ ("uniform", (0.0, 1.0)),
12
+ ("normal", (0.0, 1.0)),
13
+ ("randint", (0, 10)),
14
+ ("uniform_tails", (0.0, 1.0)),
15
+ ("clip_gaussian", (0.0, 1.0)),
16
+ ("wrap_gaussian", (0.0, 1.0, -1.0, 1.0)),
17
+ ("exponential", (1.0,)),
18
+ ("log_uniform", (0.4, 2.0)),
19
+ ("log_normal", (1.0, 0.5)),
20
+ ("spherical_sample", ()),
21
+ ("mixture_of_gaussian", ([0.0, 5.0], [1.0, 1.0], [1.0, 1.0])),
22
+ ("beta", (2.0, 5.0)),
23
+ ("poisson", (3.0,)),
24
+ ("triangular", (0.0, 1.0, 0.5)),
25
+ ("gamma", (2.0, 1.0)),
26
+ ("binomial", (10, 0.5)),
27
+ ("geometric", (0.3,)),
28
+ ]
29
+ TRACE_LEVELS = [
30
+ pf.tracer.TraceLevel.RANDOM_PARAMS,
31
+ pf.tracer.TraceLevel.RANDOM_CONTROL,
32
+ pf.tracer.TraceLevel.PRIMITIVES,
33
+ ]
34
+
35
+
36
+ def generated_function(
37
+ generator: Callable, level: pf.tracer.TraceLevel, concrete: bool
38
+ ) -> Callable:
39
+ inputs = {"rng": np.random.default_rng(17)} if concrete else {}
40
+ graph = pf.trace(generator, trace_level=level, **inputs)
41
+ source = codegen.to_python(graph, toplevel_as_maincall=False)
42
+ source = source.replace("np.random.default_rng()", "np.random.default_rng(17)")
43
+ namespace = {}
44
+ exec(source, namespace) # noqa: S102
45
+ return namespace[generator.__name__]
46
+
47
+
48
+ def test_unused_mutator_return_emits_bare_call() -> None:
49
+ def generator(rng):
50
+ pf.random.uniform(rng, 0.0, 1.0)
51
+ return pf.random.uniform(rng, 0.0, 1.0)
52
+
53
+ graph = pf.trace(generator, trace_level=pf.tracer.TraceLevel.RANDOM_PARAMS)
54
+ tree = ast.parse(codegen.to_python(graph, toplevel_as_maincall=False))
55
+ function = next(node for node in tree.body if isinstance(node, ast.FunctionDef))
56
+ assert isinstance(function.body[0], ast.Expr)
57
+ assert isinstance(function.body[1], ast.Assign)
58
+
59
+
60
+ @pytest.mark.parametrize("name,args", RANDOM_CALLS, ids=[x[0] for x in RANDOM_CALLS])
61
+ @pytest.mark.parametrize("level", TRACE_LEVELS, ids=lambda level: level.name)
62
+ @pytest.mark.parametrize("concrete", [False, True], ids=["input", "seeded"])
63
+ def test_discarded_random_draw_advances_generated_rng(
64
+ name: str, args: tuple, level: pf.tracer.TraceLevel, concrete: bool
65
+ ) -> None:
66
+ def generator(rng):
67
+ getattr(pf.random, name)(rng, *args)
68
+ return pf.random.uniform(rng, 0.0, 1.0)
69
+
70
+ generated = generated_function(generator, level, concrete)
71
+ expected = generator(np.random.default_rng(17))
72
+ actual = generated() if concrete else generated(np.random.default_rng(17))
73
+ tolerance = 1e-7 if concrete and level < pf.tracer.TraceLevel.RANDOM_PARAMS else 0
74
+ assert actual == pytest.approx(expected, rel=tolerance, abs=0)
75
+
76
+
77
+ @pytest.mark.parametrize("level", TRACE_LEVELS, ids=lambda level: level.name)
78
+ @pytest.mark.parametrize("concrete", [False, True], ids=["input", "seeded"])
79
+ def test_reversed_outputs_preserve_aliased_rng_order(
80
+ level: pf.tracer.TraceLevel, concrete: bool
81
+ ) -> None:
82
+ def generator(rng):
83
+ other = rng
84
+ width = pf.random.uniform(rng, 1.0, 2.0)
85
+ height = pf.random.uniform(other, 3.0, 4.0)
86
+ return {"height": height, "width": width}
87
+
88
+ generated = generated_function(generator, level, concrete)
89
+ expected = generator(np.random.default_rng(17))
90
+ actual = generated() if concrete else generated(np.random.default_rng(17))
91
+ tolerance = 1e-7 if concrete and level < pf.tracer.TraceLevel.RANDOM_PARAMS else 0
92
+ assert actual == pytest.approx(expected, rel=tolerance, abs=0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes