gamspy 1.18.4__py3-none-any.whl → 1.19.1__py3-none-any.whl

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.
gamspy/_container.py CHANGED
@@ -585,7 +585,47 @@ class Container(gt.Container):
585
585
 
586
586
  return gams_string
587
587
 
588
- def _load_records_from_gdx(self, load_from: str, names: Iterable[str]) -> None:
588
+ def _filter_load_symbols(
589
+ self,
590
+ symbol_names: dict[str, str] | list[str],
591
+ create_if_not_declared: bool = False,
592
+ ) -> dict[str, str] | list[str]:
593
+ if isinstance(symbol_names, list):
594
+ names = []
595
+ for name in symbol_names:
596
+ if name in self.data:
597
+ symbol = self[name]
598
+ if not isinstance(symbol, gt.Alias) and symbol.synchronize:
599
+ names.append(name)
600
+ else:
601
+ if create_if_not_declared:
602
+ names.append(name)
603
+ else:
604
+ raise ValidationError(
605
+ f"Cannot load records of `{name}` because it does not exist in the container."
606
+ )
607
+
608
+ return names
609
+
610
+ mapping = {}
611
+ for gdx_name, gamspy_name in symbol_names.items():
612
+ if gamspy_name in self.data:
613
+ if self[gamspy_name].synchronize:
614
+ mapping[gdx_name] = gamspy_name
615
+ else:
616
+ raise ValidationError(
617
+ f"Invalid renaming. `{gamspy_name}` does not exist in the container."
618
+ )
619
+
620
+ return mapping
621
+
622
+ def _load_records_from_gdx(
623
+ self,
624
+ load_from: str,
625
+ names: Iterable[str],
626
+ *,
627
+ create_if_not_declared: bool = False,
628
+ ) -> None:
589
629
  self._temp_container.read(load_from, names)
590
630
  original_state = self._options.miro_protect
591
631
  self._options.miro_protect = False
@@ -596,7 +636,12 @@ class Container(gt.Container):
596
636
  self[name].records = updated_records
597
637
  self[name].domain_labels = self[name].domain_names
598
638
  else:
599
- self._read(load_from, [name])
639
+ if create_if_not_declared:
640
+ self._read(load_from, [name])
641
+ else:
642
+ raise ValidationError(
643
+ f"Cannot load records of `{name}` because it does not exist in the container."
644
+ )
600
645
 
601
646
  self._options.miro_protect = original_state
602
647
  self._temp_container.data = {}
@@ -607,14 +652,9 @@ class Container(gt.Container):
607
652
  self._options.miro_protect = False
608
653
 
609
654
  for gdx_name, gamspy_name in names.items():
610
- if gamspy_name in self.data:
611
- updated_records = self._temp_container[gdx_name].records
612
- self[gamspy_name].records = updated_records
613
- self[gamspy_name].domain_labels = self[gamspy_name].domain_names
614
- else:
615
- raise ValidationError(
616
- f"Invalid renaming. `{gamspy_name}` does not exist in the container."
617
- )
655
+ updated_records = self._temp_container[gdx_name].records
656
+ self[gamspy_name].records = updated_records
657
+ self[gamspy_name].domain_labels = self[gamspy_name].domain_names
618
658
 
619
659
  self._options.miro_protect = original_state
620
660
  self._temp_container.data = {}
@@ -901,7 +941,7 @@ class Container(gt.Container):
901
941
  """
902
942
  if self._debugging_level != "keep":
903
943
  raise ValidationError(
904
- "`debug_level` argument of the container must be set to 'keep' to use this function."
944
+ "`debugging_level` argument of the container must be set to 'keep' to use this function."
905
945
  )
906
946
 
907
947
  gams_string = self._gams_string
@@ -916,7 +956,7 @@ class Container(gt.Container):
916
956
 
917
957
  def loadRecordsFromGdx(
918
958
  self,
919
- load_from: str,
959
+ load_from: str | Path,
920
960
  symbol_names: Iterable[str] | dict[str, str] | None = None,
921
961
  ) -> None:
922
962
  """
@@ -940,24 +980,63 @@ class Container(gt.Container):
940
980
  >>> i = Set(m, "i", records=["i1", "i2"])
941
981
  >>> m.write("test.gdx")
942
982
  >>> m2 = Container()
983
+ >>> i = Set(m2, "i")
943
984
  >>> m2.loadRecordsFromGdx("test.gdx")
944
985
  >>> print(i.records.equals(m2["i"].records))
945
986
  True
946
987
 
947
988
  """
989
+ if isinstance(load_from, Path):
990
+ load_from = str(load_from.resolve())
991
+
992
+ create_if_not_declared = symbol_names is None
948
993
  if symbol_names is None:
949
994
  # If no symbol names are given, all records in the gdx should be loaded
950
995
  symbol_names = utils._get_symbol_names_from_gdx(
951
996
  self.system_directory, load_from
952
997
  )
998
+ symbol_names = self._filter_load_symbols(
999
+ symbol_names, # type: ignore
1000
+ create_if_not_declared,
1001
+ )
1002
+ self._load_records_from_gdx(
1003
+ load_from, symbol_names, create_if_not_declared=create_if_not_declared
1004
+ )
1005
+ self._synch_with_gams()
1006
+ return
953
1007
 
954
- if isinstance(symbol_names, dict):
955
- self._load_records_with_rename(load_from, symbol_names)
1008
+ if isinstance(symbol_names, list):
1009
+ symbol_names = self._filter_load_symbols(
1010
+ symbol_names, create_if_not_declared
1011
+ )
1012
+ self._add_statement(f"$gdxIn {load_from}")
1013
+ symbols_str = " ".join(symbol_names)
1014
+ self._add_statement(f"$loadDC {symbols_str}")
1015
+ self._add_statement("$gdxIn")
1016
+ elif isinstance(symbol_names, dict):
1017
+ symbol_names = self._filter_load_symbols(
1018
+ symbol_names, create_if_not_declared
1019
+ )
1020
+ self._add_statement(f"$gdxIn {load_from}")
1021
+ symbols_str = " ".join(
1022
+ [f"{value}={key}" for key, value in symbol_names.items()] # type: ignore
1023
+ )
1024
+ self._add_statement(f"$loadDC {symbols_str}")
1025
+ self._add_statement("$gdxIn")
956
1026
  else:
957
- self._load_records_from_gdx(load_from, symbol_names)
1027
+ raise TypeError("`symbol_names` must be either a list or a dictionary.")
958
1028
 
1029
+ self._options._debug_options["gdx"] = self._gdx_out
1030
+ self._options._debug_options["gdxSymbols"] = "newOrChanged"
959
1031
  self._synch_with_gams()
960
1032
 
1033
+ if isinstance(symbol_names, dict):
1034
+ self._load_records_with_rename(load_from, symbol_names)
1035
+ else:
1036
+ self._load_records_from_gdx(
1037
+ load_from, symbol_names, create_if_not_declared=create_if_not_declared
1038
+ )
1039
+
961
1040
  def addGamsCode(self, gams_code: str) -> None:
962
1041
  """
963
1042
  Adds an arbitrary GAMS code to the generate .gms file.
gamspy/_convert.py CHANGED
@@ -338,8 +338,8 @@ def get_convert_solver_options(
338
338
  solver_options[name] = str((path / value).resolve())
339
339
 
340
340
  if options is not None:
341
- extra_options = options.model_dump(exclude_none=True)
342
- for key, value in extra_options.items():
341
+ convert_options = options.model_dump(exclude_none=True)
342
+ for key, value in convert_options.items():
343
343
  name = OPTION_RENAME_MAP.get(key, key)
344
344
  solver_options[name] = int(value) if isinstance(value, bool) else value
345
345
 
gamspy/_miro.py CHANGED
@@ -41,7 +41,9 @@ def load_miro_symbol_records(container: Container):
41
41
  for name in container._miro_input_symbols
42
42
  if not container[name]._already_loaded
43
43
  ]
44
- container._load_records_from_gdx(MIRO_GDX_IN, names)
44
+ container._load_records_from_gdx(
45
+ MIRO_GDX_IN, names, create_if_not_declared=True
46
+ )
45
47
  for name in names:
46
48
  symbol = container[name]
47
49
  symbol._already_loaded = True
@@ -54,7 +56,9 @@ def load_miro_symbol_records(container: Container):
54
56
 
55
57
  # Load records of miro output symbols
56
58
  if MIRO_GDX_OUT and container._miro_output_symbols:
57
- container._load_records_from_gdx(MIRO_GDX_OUT, container._miro_output_symbols)
59
+ container._load_records_from_gdx(
60
+ MIRO_GDX_OUT, container._miro_output_symbols, create_if_not_declared=True
61
+ )
58
62
 
59
63
  for name in container._miro_input_symbols + container._miro_output_symbols:
60
64
  container[name].modified = False
gamspy/_model.py CHANGED
@@ -368,6 +368,7 @@ class Model:
368
368
  A human-readable description of the model.
369
369
  equations : Sequence[Equation], optional
370
370
  A list or sequence of Equation objects that define the constraints of the model.
371
+ No equations are used by default.
371
372
  problem : Problem | str, optional
372
373
  The type of mathematical problem to solve (e.g., 'LP', 'MIP', 'NLP').
373
374
  Default is Problem.MIP.
@@ -380,7 +381,7 @@ class Model:
380
381
  Used for defining complementarity problems (MCP). Maps equations to their complementary variables.
381
382
  limited_variables : Sequence[ImplicitVariable], optional
382
383
  Allows limiting the domain of variables included in the model to a specific subset.
383
- external_module: str, optional
384
+ external_module : str, optional
384
385
  The name of an external module file (e.g., 'my_external.dll' or 'my_external.so')
385
386
  where external equations are implemented.
386
387
 
@@ -388,9 +389,21 @@ class Model:
388
389
  --------
389
390
  >>> import gamspy as gp
390
391
  >>> m = gp.Container()
391
- >>> v = gp.Variable(m, "v")
392
- >>> e = gp.Equation(m, "e", definition= v == 5)
393
- >>> my_model = gp.Model(m, "my_model", problem="LP", equations=[e])
392
+ >>> i = gp.Set(m, description="items")
393
+ >>> p = gp.Parameter(m, description="profits", domain=i)
394
+ >>> w = gp.Parameter(m, description="weights", domain=i)
395
+ >>> c = gp.Parameter(m, description="capacity")
396
+ >>> x = gp.Variable(m, domain=i, type=gp.VariableType.BINARY)
397
+ >>> capacity_restriction = gp.Equation(m, definition=gp.Sum(i, w[i] * x[i]) <= c)
398
+
399
+ >>> # Instantiate the Model
400
+ >>> knapsack = gp.Model(
401
+ ... m,
402
+ ... equations=m.getEquations(), # Automatically grabs all equations in the container
403
+ ... problem=gp.Problem.MIP, # Mixed Integer Program
404
+ ... sense=gp.Sense.MAX, # Maximize profit
405
+ ... objective=gp.Sum(i, p[i] * x[i]),
406
+ ... )
394
407
 
395
408
  """
396
409
 
@@ -1214,8 +1227,19 @@ class Model:
1214
1227
  >>> v = gp.Variable(m, "v")
1215
1228
  >>> e = gp.Equation(m, "e", definition= v == 5)
1216
1229
  >>> my_model = gp.Model(m, "my_model", problem="LP", equations=[e])
1217
- >>> my_model.convert("tmp", gp.FileFormat.GAMS)
1218
- >>> my_model.convert("tmp", [gp.FileFormat.GAMS, gp.FileFormat.AMPL])
1230
+
1231
+
1232
+ Convert the model into scalar GAMS format.
1233
+
1234
+
1235
+ >>> my_model.convert("tmp", gp.FileFormat.GAMS) # doctest: +SKIP
1236
+
1237
+
1238
+ Add conversion options
1239
+
1240
+
1241
+ >>> options = gp.ConvertOptions(GDXNames=False)
1242
+ >>> my_model.convert("jacobian", file_format=gp.FileFormat.GDXJacobian, options=options) # doctest: +SKIP
1219
1243
 
1220
1244
  """
1221
1245
  path = Path(path)
@@ -1277,6 +1301,33 @@ class Model:
1277
1301
  Returns
1278
1302
  -------
1279
1303
  str
1304
+
1305
+ Examples
1306
+ --------
1307
+ >>> import gamspy as gp
1308
+ >>> m = gp.Container()
1309
+ >>> i = gp.Set(m, records=["item1", "item2"])
1310
+ >>> v = gp.Variable(m, domain=i)
1311
+ >>> z = gp.Variable(m)
1312
+ >>> e = gp.Equation(m, domain=i)
1313
+ >>> e[i] = v[i] * z >= 5
1314
+ >>> model = gp.Model(m, "test", equations=[e], problem="NLP", sense="MIN", objective=z)
1315
+ >>> summary = model.solve(options=gp.Options(variable_listing_limit=10))
1316
+ >>> print(model.getVariableListing())
1317
+ v(item1)
1318
+ (.LO, .L, .UP, .M = -INF, 0, +INF, 0)
1319
+ (0) e(item1)
1320
+ <BLANKLINE>
1321
+ v(item2)
1322
+ (.LO, .L, .UP, .M = -INF, 0, +INF, 0)
1323
+ (0) e(item2)
1324
+ <BLANKLINE>
1325
+ z
1326
+ (.LO, .L, .UP, .M = -INF, 0, +INF, 0)
1327
+ (0) e(item1)
1328
+ (0) e(item2)
1329
+ <BLANKLINE>
1330
+
1280
1331
  """
1281
1332
  if not hasattr(self, "_variables"):
1282
1333
  raise ValidationError(
@@ -1301,7 +1352,7 @@ class Model:
1301
1352
  >>> # ... define model ...
1302
1353
  >>> model = gp.Model(m, "my_model", problem="LP", equations=[])
1303
1354
  >>> # In a separate thread or signal handler:
1304
- >>> # model.interrupt()
1355
+ >>> model.interrupt() # doctest: +SKIP
1305
1356
 
1306
1357
  """
1307
1358
  self.container._interrupt()
@@ -1415,12 +1466,82 @@ class Model:
1415
1466
 
1416
1467
  Examples
1417
1468
  --------
1469
+ >>> import sys
1470
+ >>> import os
1418
1471
  >>> import gamspy as gp
1419
1472
  >>> m = gp.Container()
1420
- >>> v = gp.Variable(m, "v")
1421
- >>> e = gp.Equation(m, "e", definition= v == 5)
1422
- >>> my_model = gp.Model(m, "my_model", problem="LP", equations=[e], sense="max", objective=v)
1423
- >>> solved = my_model.solve()
1473
+ >>> i = gp.Set(m, description="items")
1474
+ >>> p = gp.Parameter(m, description="profits", domain=i)
1475
+ >>> w = gp.Parameter(m, description="weights", domain=i)
1476
+ >>> c = gp.Parameter(m, description="capacity")
1477
+ >>> x = gp.Variable(m, domain=i, type=gp.VariableType.BINARY)
1478
+ >>> capacity_restriction = gp.Equation(m, definition=gp.Sum(i, w[i] * x[i]) <= c)
1479
+ >>> knapsack = gp.Model(
1480
+ ... m,
1481
+ ... equations=m.getEquations(), # Automatically grabs all equations in the container
1482
+ ... problem=gp.Problem.MIP, # Mixed Integer Program
1483
+ ... sense=gp.Sense.MAX, # Maximize profit
1484
+ ... objective=gp.Sum(i, p[i] * x[i]),
1485
+ ... )
1486
+
1487
+
1488
+ Basic usage
1489
+
1490
+
1491
+ >>> knapsack.solve() # doctest: +SKIP
1492
+
1493
+
1494
+ Change solver
1495
+
1496
+
1497
+ >>> knapsack.solve(solver="CPLEX") # doctest: +SKIP
1498
+
1499
+
1500
+ Redirect output
1501
+
1502
+
1503
+ >>> knapsack.solve(output=sys.stdout) # doctest: +SKIP
1504
+
1505
+
1506
+ Add generic solve options
1507
+
1508
+
1509
+ >>> knapsack.solve(options=gp.Options(iteration_limit=2)) # doctest: +SKIP
1510
+
1511
+
1512
+ Add solver-specific options
1513
+
1514
+
1515
+ >>> knapsack.solve(solver="CPLEX", solver_options={"preind": "off"}) # doctest: +SKIP
1516
+
1517
+
1518
+ Solve on your own machine
1519
+
1520
+
1521
+ >>> knapsack.solve() # doctest: +SKIP
1522
+
1523
+
1524
+ Solve on GAMS Engine
1525
+
1526
+
1527
+ >>> client = gp.EngineClient(
1528
+ ... host=os.getenv("ENGINE_URL", "https://<host_link>"),
1529
+ ... username=os.getenv("ENGINE_USER"),
1530
+ ... password=os.getenv("ENGINE_PASSWORD"),
1531
+ ... namespace=os.getenv("ENGINE_NAMESPACE"),
1532
+ ... )
1533
+ >>> knapsack.solve(backend="engine", client=client) # doctest: +SKIP
1534
+
1535
+
1536
+ Solve on NEOS Server
1537
+
1538
+
1539
+ >>> client = gp.NeosClient(
1540
+ ... email=os.getenv("NEOS_EMAIL"),
1541
+ ... username=os.getenv("NEOS_USER"),
1542
+ ... password=os.getenv("NEOS_PASSWORD"),
1543
+ ... )
1544
+ >>> knapsack.solve(backend="neos", client=client) # doctest: +SKIP
1424
1545
 
1425
1546
  """
1426
1547
  if output is None:
@@ -1626,7 +1747,7 @@ class Model:
1626
1747
  >>> v = gp.Variable(m, "v")
1627
1748
  >>> e = gp.Equation(m, "e", definition= v == 5)
1628
1749
  >>> my_model = gp.Model(m, "my_model", problem="LP", equations=[e])
1629
- >>> my_model.toGams("tmp") # doctest: +ELLIPSIS
1750
+ >>> my_model.toGams("tmp") # doctest: +SKIP
1630
1751
  ================================================================================
1631
1752
  GAMS (.gms) file has been generated under ...
1632
1753
  ================================================================================
@@ -1668,7 +1789,7 @@ class Model:
1668
1789
  >>> v = gp.Variable(m, "v")
1669
1790
  >>> e = gp.Equation(m, "e", definition= v == 5)
1670
1791
  >>> my_model = gp.Model(m, "my_model", problem="LP", equations=[e])
1671
- >>> my_model.toLatex("tmp") # doctest: +ELLIPSIS
1792
+ >>> my_model.toLatex("tmp") # doctest: +SKIP
1672
1793
  ================================================================================
1673
1794
  LaTeX (.tex) file has been generated under ...
1674
1795
  ================================================================================
gamspy/_model_instance.py CHANGED
@@ -318,8 +318,8 @@ class ModelInstance:
318
318
  gams_file.write(scenario_str)
319
319
 
320
320
  # Write pf file
321
- extra_options = self._prepare_gams_options()
322
- options._set_extra_options(extra_options)
321
+ hidden_options = self._prepare_hidden_options()
322
+ options._set_hidden_options(hidden_options)
323
323
  options.log_file = os.path.join(self.container.working_directory, "gamslog.dat")
324
324
  options._export(self.pf_file, self.output)
325
325
 
@@ -632,9 +632,9 @@ class ModelInstance:
632
632
 
633
633
  return will_be_modified
634
634
 
635
- def _prepare_gams_options(self) -> dict:
635
+ def _prepare_hidden_options(self) -> dict:
636
636
  scrdir = self.container._process_directory
637
- extra_options = {
637
+ hidden_options = {
638
638
  "trace": self.trace_file,
639
639
  "input": self.gms_file,
640
640
  "output": self.lst_file,
@@ -646,11 +646,11 @@ class ModelInstance:
646
646
  "solvercntr": self.solver_control_file,
647
647
  }
648
648
  if self.container._network_license:
649
- extra_options["netlicense"] = os.path.join(
649
+ hidden_options["netlicense"] = os.path.join(
650
650
  self.container._process_directory, "gamslice.dat"
651
651
  )
652
652
 
653
- return extra_options
653
+ return hidden_options
654
654
 
655
655
  def _get_columns_to_drop(self, attr: str) -> list[str]:
656
656
  attr_map = {
gamspy/_options.py CHANGED
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from typing import TYPE_CHECKING, Any, Literal
6
6
 
7
7
  from pydantic import BaseModel, ConfigDict
8
+ from typing_extensions import override
8
9
 
9
10
  from gamspy.exceptions import ValidationError
10
11
 
@@ -381,8 +382,9 @@ class Options(BaseModel):
381
382
  zero_rounding_threshold: float | None = None
382
383
  report_underflow: bool | None = None
383
384
 
385
+ @override
384
386
  def model_post_init(self, context: Any) -> None:
385
- self._extra_options: dict[str, Any] = {}
387
+ self._hidden_options: dict[str, Any] = {}
386
388
  self._debug_options: dict[str, Any] = {}
387
389
  self._solver: str | None = None
388
390
  self._problem: str | None = None
@@ -502,9 +504,9 @@ class Options(BaseModel):
502
504
  else:
503
505
  self._solver_options_file = "0"
504
506
 
505
- def _set_extra_options(self, options: dict) -> None:
507
+ def _set_hidden_options(self, options: dict) -> None:
506
508
  """Set extra options of the backend"""
507
- self._extra_options = options
509
+ self._hidden_options = options
508
510
 
509
511
  def _set_debug_options(self, options: dict) -> None:
510
512
  """Set debugging options"""
@@ -606,7 +608,7 @@ class Options(BaseModel):
606
608
  all_options["optfile"] = self._solver_options_file
607
609
 
608
610
  # Extra options
609
- all_options.update(**self._extra_options)
611
+ all_options.update(**self._hidden_options)
610
612
  all_options.update(**self._debug_options)
611
613
 
612
614
  if self._frame is not None:
@@ -753,7 +755,7 @@ class ConvertOptions(BaseModel):
753
755
  >>> eq[i] = x[i] >= 1
754
756
  >>> model = gp.Model(m, "test_model", equations=[eq], problem="LP", sense="min", objective=gp.Sum(i, x[i]))
755
757
  >>> options = gp.ConvertOptions(GDXNames=False)
756
- >>> # model.convert("jacobian", file_format=gp.FileFormat.GDXJacobian, options=options)
758
+ >>> model.convert("jacobian", file_format=gp.FileFormat.GDXJacobian, options=options) # doctest: +SKIP
757
759
 
758
760
  """
759
761
 
gamspy/_symbols/symbol.py CHANGED
@@ -46,6 +46,15 @@ class Symbol:
46
46
  Returns
47
47
  -------
48
48
  str
49
+
50
+ Examples
51
+ --------
52
+ >>> import gamspy as gp
53
+ >>> m = gp.Container()
54
+ >>> i = gp.Set(m, "i")
55
+ >>> print(i.latexRepr())
56
+ i
57
+
49
58
  """
50
59
  return self._latex_name
51
60
 
gamspy/_workspace.py CHANGED
@@ -18,7 +18,7 @@ def validate_arguments(
18
18
  if working_directory == "":
19
19
  raise ValidationError("`working_directory` cannot be an empty string.")
20
20
 
21
- # Validate debug_level
21
+ # Validate debugging_level
22
22
  if not isinstance(debugging_level, str) or debugging_level not in DEBUGGING_LEVELS:
23
23
  raise ValidationError(f"debugging level must be one of {DEBUGGING_LEVELS}")
24
24
 
@@ -280,7 +280,7 @@ def _generate_ray(
280
280
  return x_var, b_var, eqs
281
281
 
282
282
 
283
- def points_to_intervals(
283
+ def _points_to_intervals(
284
284
  x_points: typing.Sequence[int | float],
285
285
  y_points: typing.Sequence[int | float],
286
286
  discontinuous_points: typing.Sequence[int],
@@ -422,7 +422,7 @@ def pwl_interval_formulation(
422
422
  combined_indices = list({*discontinuous_indices, *none_indices})
423
423
  equations = []
424
424
 
425
- intervals = points_to_intervals(x_points, y_points, combined_indices)
425
+ intervals = _points_to_intervals(x_points, y_points, combined_indices)
426
426
  lowerbounds_input = []
427
427
  upperbounds_input = []
428
428
  slopes_input = []
gamspy/math/activation.py CHANGED
@@ -48,6 +48,22 @@ def tanh(x: Variable) -> tuple[Variable, list[Equation]]:
48
48
  Convenience wrapper that uses gamspy.math.tanh. Unlike gamspy.math.tanh,
49
49
  this function creates a new variable and the equation that
50
50
  sets it to follow formulations structure.
51
+
52
+ Parameters
53
+ ----------
54
+ x : Variable
55
+
56
+ Returns
57
+ -------
58
+ tuple[Variable, list[Equation]]
59
+
60
+ Examples
61
+ --------
62
+ >>> import gamspy as gp
63
+ >>> m = gp.Container()
64
+ >>> v1 = gp.Variable(m)
65
+ >>> v2, eqs = gp.math.activation.tanh(v1)
66
+
51
67
  """
52
68
  y = x.container.addVariable(domain=x.domain)
53
69
  set_y = x.container.addEquation(domain=x.domain)